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

MerginMaps / input / 6391178811

03 Oct 2023 09:29AM UTC coverage: 61.787% (-0.1%) from 61.908%
6391178811

push

github

iiLubos
Use distance unit from project's CRS for Stakeout panel

7580 of 12268 relevant lines covered (61.79%)

102.22 hits per line

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

52.45
/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( const double distanceInMeters, int precision )
×
164
{
165
  QgsCoordinateReferenceSystem projectCrs = QgsProject::instance()->crs();
×
166

167
  switch ( projectCrs.mapUnits() )
×
168
  {
169
    case Qgis::DistanceUnit::Meters:
×
170
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters, 'f', precision ), "m" );
×
171
    case Qgis::DistanceUnit::Kilometers:
×
172
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters / 1000, 'f', precision ), "km" );
×
173
    case Qgis::DistanceUnit::Feet:
×
174
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters * 3.28084, 'f', precision ), "feet" );
×
175
    case Qgis::DistanceUnit::NauticalMiles:
×
176
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters / 1852, 'f', precision ), "nautical miles" );
×
177
    case Qgis::DistanceUnit::Yards:
×
178
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters * 1.09361, 'f', precision ), "yards" );
×
179
    case Qgis::DistanceUnit::Miles:
×
180
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters / 1609.34, 'f', precision ), "miles" );
×
181
    case Qgis::DistanceUnit::Centimeters:
×
182
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters * 100, 'f', precision ), "cm" );
×
183
    case Qgis::DistanceUnit::Millimeters:
×
184
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters * 1000, 'f', precision ), "mm" );
×
185
    case Qgis::DistanceUnit::Inches:
×
186
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters * 39.3701, 'f', precision ), "inches" );
×
187
    default:
×
188
      return QString( "%1 %2" ).arg( QString::number( distanceInMeters, 'f', precision ), "m" );
×
189
  }
190
}
×
191

192
QString InputUtils::formatDateTimeDiff( const QDateTime &tMin, const QDateTime &tMax )
15✔
193
{
194
  qint64 daysDiff = tMin.daysTo( tMax );
15✔
195

196
  // datetime is invalid
197
  if ( daysDiff < 0 )
15✔
198
  {
199
    return INVALID_DATETIME_STR;
×
200
  }
201

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

250
  return INVALID_DATETIME_STR;
251
}
252

253
void InputUtils::setExtentToFeature( const FeatureLayerPair &pair, InputMapSettings *mapSettings, double panelOffsetRatio )
×
254
{
255

256
  if ( !mapSettings )
×
257
    return;
×
258

259
  if ( !pair.layer() )
×
260
    return;
×
261

262
  if ( !pair.feature().isValid() )
×
263
    return;
×
264

265
  QgsGeometry geom = pair.feature().geometry();
×
266
  if ( geom.isNull() || !geom.constGet() )
×
267
    return;
×
268

269
  QgsRectangle bbox = mapSettings->mapSettings().layerExtentToOutputExtent( pair.layer(), geom.boundingBox() );
×
270
  QgsRectangle currentExtent = mapSettings->mapSettings().extent();
×
271
  QgsPointXY currentExtentCenter = currentExtent.center();
×
272
  QgsPointXY featureCenter = bbox.center();
×
273

274
  double panelOffset = ( currentExtent.yMaximum() - currentExtent.yMinimum() ) * panelOffsetRatio / 2;
×
275
  double offsetX = currentExtentCenter.x() - featureCenter.x();
×
276
  double offsetY = currentExtentCenter.y() - featureCenter.y();
×
277
  currentExtent.setXMinimum( currentExtent.xMinimum() - offsetX );
×
278
  currentExtent.setXMaximum( currentExtent.xMaximum() - offsetX );
×
279
  currentExtent.setYMinimum( currentExtent.yMinimum() - offsetY - panelOffset );
×
280
  currentExtent.setYMaximum( currentExtent.yMaximum() - offsetY - panelOffset );
×
281
  mapSettings->setExtent( currentExtent );
×
282
}
×
283

284
double InputUtils::convertCoordinateString( const QString &rationalValue )
×
285
{
286
  QStringList values = rationalValue.split( "," );
×
287
  if ( values.size() != 3 ) return 0;
×
288

289
  double degrees = ratherZeroThanNaN( convertRationalNumber( values.at( 0 ) ) );
×
290
  double minutes = ratherZeroThanNaN( convertRationalNumber( values.at( 1 ) ) );
×
291
  double seconds = ratherZeroThanNaN( convertRationalNumber( values.at( 2 ) ) );
×
292

293
  double result = degrees + minutes / 60 + seconds / 3600;
×
294
  return result;
×
295
}
×
296

297
QString InputUtils::degreesString( const QgsPoint &point )
×
298
{
299
  if ( point.isEmpty() )
×
300
  {
301
    return QLatin1String();
×
302
  }
303

304
  // QGeoCoordinate formatter uses lat/long order, but we (and QGIS) use long/lat order,
305
  // so here we need to first pass y and then x.
306
  return QGeoCoordinate( point.y(), point.x() ).toString( QGeoCoordinate::DegreesMinutesWithHemisphere );
×
307
}
308

309
double InputUtils::convertRationalNumber( const QString &rationalValue )
×
310
{
311
  if ( rationalValue.isEmpty() )
×
312
    return std::numeric_limits<double>::quiet_NaN();
×
313

314
  QStringList number = rationalValue.split( "/" );
×
315
  if ( number.size() != 2 )
×
316
    return std::numeric_limits<double>::quiet_NaN();
×
317

318
  double numerator = number.at( 0 ).toDouble();
×
319
  double denominator = number.at( 1 ).toDouble();
×
320
  if ( denominator == 0 )
×
321
    return denominator;
×
322

323
  return numerator / denominator;
×
324
}
×
325

326
double InputUtils::mapSettingsScale( InputMapSettings *ms )
×
327
{
328
  if ( !ms ) return 1;
×
329
  return 1 / ms->mapUnitsPerPixel();
×
330
}
331

332
double InputUtils::mapSettingsOffsetX( InputMapSettings *ms )
×
333
{
334
  if ( !ms ) return 0;
×
335
  return -ms->visibleExtent().xMinimum();
×
336
}
337

338
double InputUtils::mapSettingsOffsetY( InputMapSettings *ms )
×
339
{
340
  if ( !ms ) return 0;
×
341
  return -ms->visibleExtent().yMaximum();
×
342
}
343

344
double InputUtils::mapSettingsDPR( InputMapSettings *ms )
×
345
{
346
  if ( !ms ) return 1;
×
347
  return ms->devicePixelRatio();
×
348
}
349

350
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, const QgsCoordinateReferenceSystem &destinationCRS, const QgsCoordinateTransformContext &context )
1✔
351
{
352
  QgsGeometry g( geometry );
1✔
353

354
  QgsCoordinateTransform ct( sourceCRS, destinationCRS, context );
1✔
355
  if ( !ct.isShortCircuited() )
1✔
356
  {
357
    try
358
    {
359
      g.transform( ct );
×
360
    }
361
    catch ( QgsCsException &e )
×
362
    {
363
      Q_UNUSED( e )
364
      return QgsGeometry();
×
365
    }
×
366
  }
367

368
  return g;
1✔
369
}
1✔
370

371
QgsGeometry InputUtils::transformGeometry( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, QgsVectorLayer *targetLayer )
1✔
372
{
373
  if ( !targetLayer || !targetLayer->isValid() )
1✔
374
  {
375
    return QgsGeometry();
×
376
  }
377

378
  return transformGeometry( geometry, sourceCRS, targetLayer->crs(), targetLayer->transformContext() );
2✔
379
}
380

381
QgsGeometry InputUtils::transformGeometryToMapWithLayer( const QgsGeometry &geometry, QgsVectorLayer *sourceLayer, InputMapSettings *targetSettings )
×
382
{
383
  if ( !sourceLayer || !sourceLayer->isValid() || !targetSettings )
×
384
  {
385
    return QgsGeometry();
×
386
  }
387

388
  return transformGeometry( geometry, sourceLayer->crs(), targetSettings->destinationCrs(), targetSettings->transformContext() );
×
389
}
390

391
QgsGeometry InputUtils::transformGeometryToMapWithCRS( const QgsGeometry &geometry, const QgsCoordinateReferenceSystem &sourceCRS, InputMapSettings *targetSettings )
×
392
{
393
  if ( !targetSettings )
×
394
  {
395
    return QgsGeometry();
×
396
  }
397

398
  return transformGeometry( geometry, sourceCRS, targetSettings->destinationCrs(), targetSettings->transformContext() );
×
399
}
400

401
QgsGeometry InputUtils::extractGeometry( const FeatureLayerPair &pair )
×
402
{
403
  if ( !pair.isValid() )
×
404
    return QgsGeometry();
×
405

406
  return pair.feature().geometry();
×
407
}
408

409
QString InputUtils::geometryLengthAsString( const QgsGeometry &geometry )
×
410
{
411
  QgsDistanceArea distanceArea;
×
412
  distanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
×
413
  distanceArea.setSourceCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsCoordinateTransformContext() );
×
414

415
  qreal length = distanceArea.measureLength( geometry );
×
416

417
  if ( qgsDoubleNear( length, 0 ) )
×
418
  {
419
    return "0 m";
×
420
  }
421

422
  return distanceArea.formatDistance( length, 2, distanceArea.lengthUnits() );
×
423
}
×
424

425
static void addLineString( const QgsLineString *line, QVector<double> &data )
×
426
{
427
  data << line->numPoints();
×
428
  const double *x = line->xData();
×
429
  const double *y = line->yData();
×
430
  for ( int i = 0; i < line->numPoints(); ++i )
×
431
  {
432
    data << x[i] << y[i];
×
433
  }
434
}
×
435

436
static void addSingleGeometry( const QgsAbstractGeometry *geom, Qgis::GeometryType type, QVector<double> &data )
×
437
{
438
  switch ( type )
×
439
  {
440
    case Qgis::GeometryType::Point:
×
441
    {
442
      const QgsPoint *point = qgsgeometry_cast<const QgsPoint *>( geom );
×
443
      if ( point )
×
444
      {
445
        data << 0 << point->x() << point->y();
×
446
      }
447
      break;
×
448
    }
449

450
    case Qgis::GeometryType::Line:
×
451
    {
452
      const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( geom );
×
453
      if ( line )
×
454
      {
455
        data << 1;
×
456
        addLineString( line, data );
×
457
      }
458
      break;
×
459
    }
460

461
    case Qgis::GeometryType::Polygon:
×
462
    {
463
      const QgsPolygon *poly = qgsgeometry_cast<const QgsPolygon *>( geom );
×
464
      if ( poly )
×
465
      {
466
        if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->exteriorRing() ) )
×
467
        {
468
          data << 2;
×
469
          addLineString( line, data );
×
470
        }
471
        for ( int i = 0; i < poly->numInteriorRings(); ++i )
×
472
        {
473
          if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->interiorRing( i ) ) )
×
474
          {
475
            data << 2;
×
476
            addLineString( line, data );
×
477
          }
478
        }
479
      }
480
      break;
×
481
    }
482

483
    case Qgis::GeometryType::Unknown:
×
484
    case Qgis::GeometryType::Null:
485
      break;
×
486
  }
487
}
×
488

489
QVector<double> InputUtils::extractGeometryCoordinates( const QgsGeometry &geometry )
×
490
{
491
  if ( geometry.isNull() )
×
492
    return QVector<double>();
×
493

494
  QVector<double> data;
×
495

496
  const QgsAbstractGeometry *geom = geometry.constGet();
×
497
  Qgis::GeometryType geomType = geometry.type();
×
498
  const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
×
499
  if ( collection && !collection->isEmpty() )
×
500
  {
501
    for ( int i = 0; i < collection->numGeometries(); ++i )
×
502
    {
503
      addSingleGeometry( collection->geometryN( i ), geomType, data );
×
504
    }
505
  }
506
  else
507
  {
508
    addSingleGeometry( geom, geomType, data );
×
509
  }
510

511
  return data;
×
512
}
×
513

514
QString InputUtils::filesToString( QList<MerginFile> files )
×
515
{
516
  QStringList resultList;
×
517
  for ( MerginFile file : files )
×
518
  {
519
    resultList << file.path;
×
520
  }
×
521
  return resultList.join( ", " );
×
522
}
×
523

524
QString InputUtils::bytesToHumanSize( double bytes )
8✔
525
{
526
  const int precision = 1;
8✔
527
  if ( bytes < 1e-5 )
8✔
528
  {
529
    return "0.0";
×
530
  }
531
  else if ( bytes < 1024.0 * 1024.0 )
8✔
532
  {
533
    return QString::number( bytes / 1024.0, 'f', precision ) + " KB";
×
534
  }
535
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 )
8✔
536
  {
537
    return QString::number( bytes / 1024.0 / 1024.0, 'f', precision ) + " MB";
×
538
  }
539
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 * 1024.0 )
8✔
540
  {
541
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " GB";
16✔
542
  }
543
  else
544
  {
545
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " TB";
×
546
  }
547
}
548

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

558
bool InputUtils::isBluetoothTurnedOn()
×
559
{
560
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
561
  {
562
    return mAndroidUtils->isBluetoothTurnedOn();
×
563
  }
564
  return true;
×
565
}
566

567
void InputUtils::turnBluetoothOn()
×
568
{
569
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
570
  {
571
    mAndroidUtils->turnBluetoothOn();
×
572
  }
573
}
×
574

575
void InputUtils::quitApp()
×
576
{
577
  if ( appPlatform() == QStringLiteral( "android" ) )
×
578
  {
579
    AndroidUtils::quitApp();
×
580
  }
581
  else
582
  {
583
    QCoreApplication::quit();
×
584
  }
585
}
×
586

587
QString InputUtils::appPlatform()
21✔
588
{
589
#if defined( ANDROID )
590
  const QString platform = "android";
591
#elif defined( Q_OS_IOS )
592
  const QString platform = "ios";
593
#elif defined( Q_OS_WIN32 )
594
  const QString platform = "win";
595
#elif defined( Q_OS_LINUX )
596
  const QString platform = "linux";
21✔
597
#elif defined( Q_OS_MAC )
598
  const QString platform = "macos";
599
#else
600
  const QString platform = "unknown";
601
#endif
602
  return platform;
21✔
603
}
604

605
bool InputUtils::isMobilePlatform()
3✔
606
{
607
  QString platform = appPlatform();
3✔
608
  return platform == QStringLiteral( "android" ) || platform == QStringLiteral( "ios" );
12✔
609
}
3✔
610

611
void InputUtils::onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level )
17✔
612
{
613
  QString levelStr;
17✔
614
  switch ( level )
17✔
615
  {
616
    case Qgis::MessageLevel::Warning:
17✔
617
      levelStr = "Warning";
17✔
618
      break;
17✔
619
    case Qgis::MessageLevel::Critical:
×
620
      levelStr = "Error";
×
621
      break;
×
622
    default:
×
623
      break;
×
624
  }
625

626
  CoreUtils::log( "QGIS " + tag, levelStr + ": " + message );
17✔
627
}
17✔
628

629
bool InputUtils::cpDir( const QString &srcPath, const QString &dstPath, bool onlyDiffable )
57✔
630
{
631
  bool result  = true;
57✔
632
  QDir parentDstDir( QFileInfo( dstPath ).path() );
114✔
633
  if ( !parentDstDir.mkpath( dstPath ) )
57✔
634
  {
635
    CoreUtils::log( "cpDir", QString( "Cannot make path %1" ).arg( dstPath ) );
×
636
    return false;
×
637
  }
638

639
  QDir srcDir( srcPath );
57✔
640
  const QFileInfoList fileInfoList = srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden );
57✔
641
  foreach ( const QFileInfo &info, fileInfoList )
546✔
642
  {
643
    QString fileName = info.fileName();
489✔
644

645
#ifdef ANDROID
646
    // https://bugreports.qt.io/browse/QTBUG-114219
647
    if ( fileName.startsWith( "assets:/" ) )
648
    {
649
      fileName.remove( 0, 8 );
650
    }
651
#endif
652

653
    QString srcItemPath = srcPath + "/" + fileName;
489✔
654
    QString dstItemPath = dstPath + "/" + fileName;
489✔
655

656
    if ( info.isDir() )
489✔
657
    {
658
      if ( !cpDir( srcItemPath, dstItemPath ) )
2✔
659
      {
660
        CoreUtils::log( "cpDir", QString( "Cannot copy a dir from %1 to %2" ).arg( srcItemPath ).arg( dstItemPath ) );
×
661
        result = false;
×
662
      }
663
    }
664
    else if ( info.isFile() )
487✔
665
    {
666
      if ( onlyDiffable && !MerginApi::isFileDiffable( fileName ) )
487✔
667
        continue;
×
668

669
      if ( !QFile::copy( srcItemPath, dstItemPath ) )
487✔
670
      {
671
        if ( !QFile::remove( dstItemPath ) )
×
672
        {
673
          CoreUtils::log( "cpDir", QString( "Cannot remove a file from %1" ).arg( dstItemPath ) );
×
674
          result =  false;
×
675
        }
676
        if ( !QFile::copy( srcItemPath, dstItemPath ) )
×
677
        {
678
          CoreUtils::log( "cpDir", QString( "Cannot overwrite a file %1 with %2" ).arg( dstItemPath ).arg( dstItemPath ) );
×
679
          result =  false;
×
680
        }
681
      }
682
      QFile::setPermissions( dstItemPath, QFile::ReadUser | QFile::WriteUser | QFile::ReadOwner | QFile::WriteOwner );
487✔
683
    }
684
    else
685
    {
686
      CoreUtils::log( "cpDir", QString( "Unhandled item %1 in cpDir" ).arg( info.filePath() ) );
×
687
    }
688
  }
546✔
689
  return result;
57✔
690
}
57✔
691

692
QString InputUtils::renameWithDateTime( const QString &srcPath, const QDateTime &dateTime )
×
693
{
694
  if ( QFile::exists( srcPath ) )
×
695
  {
696
    QFileInfo info( srcPath );
×
697
    QString timestamp = ( dateTime.isValid() ) ? dateTime.toString( DATE_TIME_FORMAT ) : QDateTime::currentDateTime().toString( DATE_TIME_FORMAT );
×
698
    QString newFilename = QString( "%1.%2" ).arg( timestamp ).arg( info.suffix() );
×
699
    QString newPath( info.absolutePath() + "/" + newFilename );
×
700

701
    if ( QFile::rename( srcPath, newPath ) ) return newPath;
×
702
  }
×
703

704
  return QString();
×
705
}
706

707
bool InputUtils::renameFile( const QString &srcPath, const QString &dstPath )
1✔
708
{
709
  QFileInfo fi( dstPath );
1✔
710
  if ( !InputUtils::createDirectory( fi.absoluteDir().path() ) )
1✔
711
  {
712
    return false;
×
713
  }
714
  return QFile::rename( srcPath, dstPath );
1✔
715
}
1✔
716

717
void InputUtils::showNotification( const QString &message )
×
718
{
719
  emit showNotificationRequested( message );
×
720
}
×
721

722
double InputUtils::ratherZeroThanNaN( double d )
×
723
{
724
  return ( isnan( d ) ) ? 0.0 : d;
×
725
}
726

727
/**
728
 * Makes QgsCoordinateReferenceSystem::fromEpsgId accessible for QML components
729
 */
730
QgsCoordinateReferenceSystem InputUtils::coordinateReferenceSystemFromEpsgId( long epsg )
12✔
731
{
732
  return QgsCoordinateReferenceSystem::fromEpsgId( epsg );
12✔
733
}
734

735
QgsPointXY InputUtils::pointXY( double x, double y )
1✔
736
{
737
  return QgsPointXY( x, y );
1✔
738
}
739

740
QgsPoint InputUtils::point( double x, double y, double z, double m )
2✔
741
{
742
  return QgsPoint( x, y, z, m );
2✔
743
}
744

745
QgsGeometry InputUtils::emptyGeometry()
×
746
{
747
  return QgsGeometry();
×
748
}
749

750
QgsFeature InputUtils::emptyFeature()
×
751
{
752
  return QgsFeature();
×
753
}
754

755
bool InputUtils::isEmptyGeometry( const QgsGeometry &geometry )
×
756
{
757
  return geometry.isEmpty();
×
758
}
759

760
QgsPoint InputUtils::coordinateToPoint( const QGeoCoordinate &coor )
×
761
{
762
  return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() );
×
763
}
764

765
QgsPointXY InputUtils::transformPointXY( const QgsCoordinateReferenceSystem &srcCrs,
27✔
766
    const QgsCoordinateReferenceSystem &destCrs,
767
    const QgsCoordinateTransformContext &context,
768
    const QgsPointXY &srcPoint )
769
{
770
  // we do not want to transform empty points,
771
  // QGIS would convert them to a valid (0, 0) points
772
  if ( srcPoint.isEmpty() )
27✔
773
  {
774
    return QgsPointXY();
×
775
  }
776

777
  try
778
  {
779
    QgsCoordinateTransform ct( srcCrs, destCrs, context );
27✔
780
    if ( ct.isValid() )
27✔
781
    {
782
      if ( !ct.isShortCircuited() )
27✔
783
      {
784
        const QgsPointXY pt = ct.transform( srcPoint );
6✔
785
        return pt;
6✔
786
      }
787
      else
788
      {
789
        return srcPoint;
21✔
790
      }
791
    }
792
  }
27✔
793
  catch ( QgsCsException &cse )
×
794
  {
795
    Q_UNUSED( cse )
796
  }
×
797

798
  return QgsPointXY();
×
799
}
800

801
QgsPoint InputUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs,
28✔
802
                                     const QgsCoordinateReferenceSystem &destCrs,
803
                                     const QgsCoordinateTransformContext &context,
804
                                     const QgsPoint &srcPoint )
805
{
806
  // we do not want to transform empty points,
807
  // QGIS would convert them to a valid (0, 0) points
808
  if ( srcPoint.isEmpty() )
28✔
809
  {
810
    return QgsPoint();
1✔
811
  }
812

813
  try
814
  {
815
    QgsCoordinateTransform ct( srcCrs, destCrs, context );
27✔
816
    if ( ct.isValid() )
27✔
817
    {
818
      if ( !ct.isShortCircuited() )
27✔
819
      {
820
        const QgsPointXY transformed = ct.transform( srcPoint.x(), srcPoint.y() );
6✔
821
        const QgsPoint pt( transformed.x(), transformed.y(), srcPoint.z(), srcPoint.m() );
6✔
822
        return pt;
6✔
823
      }
6✔
824
      else
825
      {
826
        return srcPoint;
21✔
827
      }
828
    }
829
  }
27✔
830
  catch ( QgsCsException &cse )
×
831
  {
832
    Q_UNUSED( cse )
833
  }
×
834

835
  return QgsPoint();
×
836
}
837

838
QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint )
×
839
{
840
  if ( !mapSettings || srcPoint.isEmpty() )
×
841
    return QPointF();
×
842

843
  QgsPoint mapcrsPoint = transformPoint( srcCrs, mapSettings->destinationCrs(), mapSettings->transformContext(), srcPoint );
×
844
  return mapSettings->coordinateToScreen( mapcrsPoint );
×
845
}
×
846

847
double InputUtils::screenUnitsToMeters( InputMapSettings *mapSettings, int baseLengthPixels )
30✔
848
{
849
  if ( mapSettings == nullptr ) return 0.0;
30✔
850

851
  QgsDistanceArea mDistanceArea;
30✔
852
  mDistanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
30✔
853
  mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
30✔
854

855
  // calculate the geographic distance from the central point of extent
856
  // to the specified number of points on the right side
857
  QSize s = mapSettings->outputSize();
30✔
858
  QPoint pointCenter( s.width() / 2, s.height() / 2 );
30✔
859
  QgsPointXY p1 = mapSettings->screenToCoordinate( pointCenter );
30✔
860
  QgsPointXY p2 = mapSettings->screenToCoordinate( pointCenter + QPoint( baseLengthPixels, 0 ) );
30✔
861
  return mDistanceArea.measureLine( p1, p2 );
30✔
862
}
30✔
863

864
QgsPoint InputUtils::mapPointToGps( QPointF mapPosition, InputMapSettings *mapSettings )
5✔
865
{
866
  if ( !mapSettings )
5✔
867
    return QgsPoint();
×
868

869
  if ( mapPosition.isNull() )
5✔
870
    return QgsPoint();
×
871

872
  QgsPoint positionMapCrs = mapSettings->screenToCoordinate( mapPosition );
5✔
873
  QgsCoordinateReferenceSystem crsGPS = coordinateReferenceSystemFromEpsgId( 4326 );
5✔
874

875
  const QgsPointXY transformedXY = transformPoint(
10✔
876
                                     mapSettings->destinationCrs(),
10✔
877
                                     crsGPS,
878
                                     QgsCoordinateTransformContext(),
10✔
879
                                     positionMapCrs
880
                                   );
5✔
881

882
  if ( transformedXY.isEmpty() )
5✔
883
  {
884
    // point could not be transformed
885
    return QgsPoint();
1✔
886
  }
887

888
  return QgsPoint( transformedXY );
4✔
889
}
5✔
890

891
bool InputUtils::fileExists( const QString &path )
8✔
892
{
893
  QFileInfo check_file( path );
8✔
894
  // check if file exists and if yes: Is it really a file and no directory?
895
  return ( check_file.exists() && check_file.isFile() );
16✔
896
}
8✔
897

898
QString InputUtils::resolveTargetDir( const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
7✔
899
{
900
  QString expression;
7✔
901
  QMap<QString, QVariant> collection = config.value( QStringLiteral( "PropertyCollection" ) ).toMap();
21✔
902
  QMap<QString, QVariant> props = collection.value( QStringLiteral( "properties" ) ).toMap();
21✔
903

904
  if ( !props.isEmpty() )
7✔
905
  {
906
    QMap<QString, QVariant> propertyRootPath = props.value( QStringLiteral( "propertyRootPath" ) ).toMap();
3✔
907
    expression = propertyRootPath.value( QStringLiteral( "expression" ), QString() ).toString();
2✔
908
  }
1✔
909

910
  if ( !expression.isEmpty() )
7✔
911
  {
912
    return evaluateExpression( pair, activeProject, expression );
1✔
913
  }
914
  else
915
  {
916
    QString defaultRoot = config.value( QStringLiteral( "DefaultRoot" ) ).toString();
18✔
917
    if ( defaultRoot.isEmpty() )
6✔
918
    {
919
      return homePath;
5✔
920
    }
921
    else
922
    {
923
      return defaultRoot;
1✔
924
    }
925
  }
6✔
926
}
7✔
927

928
QString InputUtils::resolvePrefixForRelativePath( int relativeStorageMode, const QString &homePath, const QString &targetDir )
4✔
929
{
930
  if ( relativeStorageMode == 1 )
4✔
931
  {
932
    return homePath;
2✔
933
  }
934
  else if ( relativeStorageMode == 2 )
2✔
935
  {
936
    return targetDir;
1✔
937
  }
938
  else
939
  {
940
    return QString();
1✔
941
  }
942
}
943

944
QString InputUtils::getAbsolutePath( const QString &path, const QString &prefixPath )
5✔
945
{
946
  return ( prefixPath.isEmpty() ) ? path : QStringLiteral( "%1/%2" ).arg( prefixPath ).arg( path );
9✔
947
}
948

949
QString InputUtils::resolvePath( const QString &path, const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
3✔
950
{
951
  int relativeStorageMode = config.value( QStringLiteral( "RelativeStorage" ) ).toInt();
6✔
952
  QString targetDir = resolveTargetDir( homePath, config, pair, activeProject );
3✔
953
  QString prefixToRelativePath = resolvePrefixForRelativePath( relativeStorageMode, homePath, targetDir );
3✔
954

955
  return getAbsolutePath( path, prefixToRelativePath );
6✔
956
}
3✔
957

958
QString InputUtils::getRelativePath( const QString &path, const QString &prefixPath )
5✔
959
{
960
  QString modPath = path;
5✔
961
  QString filePrefix( "file://" );
5✔
962

963
  if ( path.startsWith( filePrefix ) )
5✔
964
  {
965
    modPath = modPath.replace( filePrefix, QString() );
1✔
966
  }
967

968
  if ( prefixPath.isEmpty() ) return modPath;
5✔
969

970
  // Do not use a canonical path for non-existing path
971
  if ( !QFileInfo( path ).exists() )
4✔
972
  {
973
    if ( !prefixPath.isEmpty() && modPath.startsWith( prefixPath ) )
4✔
974
    {
975
      return modPath.replace( prefixPath, QString() );
3✔
976
    }
977
  }
978
  else
979
  {
980
    QDir absoluteDir( modPath );
×
981
    QDir prefixDir( prefixPath );
×
982
    QString canonicalPath = absoluteDir.canonicalPath();
×
983
    QString prefixCanonicalPath = prefixDir.canonicalPath() + "/";
×
984

985
    if ( prefixCanonicalPath.length() > 1 && canonicalPath.startsWith( prefixCanonicalPath ) )
×
986
    {
987
      return canonicalPath.replace( prefixCanonicalPath, QString() );
×
988
    }
989
  }
×
990

991
  return QString();
1✔
992
}
5✔
993

994
void InputUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
×
995
{
996
  QgsMessageLog::logMessage( message, tag, level );
×
997
}
×
998

999
void InputUtils::log( const QString &context, const QString &message )
×
1000
{
1001
  CoreUtils::log( context, message );
×
1002
}
×
1003

1004
const QUrl InputUtils::getThemeIcon( const QString &name )
1✔
1005
{
1006
  QString path = QStringLiteral( "qrc:/%1.svg" ).arg( name );
1✔
1007
  QgsDebugMsgLevel( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ), 2 );
1008
  return QUrl( path );
2✔
1009
}
1✔
1010

1011
const QUrl InputUtils::getEditorComponentSource( const QString &widgetName, const QVariantMap &config, const QgsField &field )
2✔
1012
{
1013
  QString path( "../editor/input%1.qml" );
2✔
1014

1015
  if ( widgetName == QStringLiteral( "range" ) )
2✔
1016
  {
1017
    if ( config.contains( "Style" ) )
×
1018
    {
1019
      if ( config["Style"] == QStringLiteral( "Slider" ) )
×
1020
      {
1021
        return QUrl( path.arg( QLatin1String( "rangeslider" ) ) );
×
1022
      }
1023
      else if ( config["Style"] == QStringLiteral( "SpinBox" ) )
×
1024
      {
1025
        return QUrl( path.arg( QLatin1String( "rangeeditable" ) ) );
×
1026
      }
1027
    }
1028
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1029
  }
1030

1031
  if ( field.name().contains( "qrcode", Qt::CaseInsensitive ) || field.alias().contains( "qrcode", Qt::CaseInsensitive ) )
2✔
1032
  {
1033
    return QUrl( path.arg( QStringLiteral( "qrcodereader" ) ) );
×
1034
  }
1035

1036
  if ( widgetName == QStringLiteral( "textedit" ) )
2✔
1037
  {
1038
    if ( config.value( "IsMultiline" ).toBool() )
×
1039
    {
1040
      return QUrl( path.arg( QStringLiteral( "texteditmultiline" ) ) );
×
1041
    }
1042
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1043
  }
1044

1045
  if ( widgetName == QStringLiteral( "valuerelation" ) )
2✔
1046
  {
1047
    const QgsMapLayer *referencedLayer = QgsProject::instance()->mapLayer( config.value( "Layer" ).toString() );
×
1048
    const QgsVectorLayer *layer = qobject_cast<const QgsVectorLayer *>( referencedLayer );
×
1049

1050
    if ( layer )
×
1051
    {
1052
      int featuresCount = layer->dataProvider()->featureCount();
×
1053
      if ( featuresCount > 4 )
×
1054
        return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1055
    }
1056

1057
    if ( config.value( "AllowMulti" ).toBool() )
×
1058
    {
1059
      return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1060
    }
1061

1062
    return QUrl( path.arg( QLatin1String( "valuerelationcombobox" ) ) );
×
1063
  }
1064

1065
  QStringList supportedWidgets = { QStringLiteral( "textedit" ),
×
1066
                                   QStringLiteral( "valuemap" ),
2✔
1067
                                   QStringLiteral( "valuerelation" ),
2✔
1068
                                   QStringLiteral( "checkbox" ),
2✔
1069
                                   QStringLiteral( "externalresource" ),
2✔
1070
                                   QStringLiteral( "datetime" ),
2✔
1071
                                   QStringLiteral( "range" ),
2✔
1072
                                   QStringLiteral( "relation" ),
2✔
1073
                                   QStringLiteral( "relationreference" )
2✔
1074
                                 };
38✔
1075
  if ( supportedWidgets.contains( widgetName ) )
2✔
1076
  {
1077
    return QUrl( path.arg( widgetName ) );
2✔
1078
  }
1079
  else
1080
  {
1081
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
2✔
1082
  }
1083
}
2✔
1084

1085
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field )
8✔
1086
{
1087
  if ( field.isNumeric() )
8✔
1088
    return getEditorWidgetSetup( field, QStringLiteral( "Range" ) );
12✔
1089
  else if ( field.isDateOrTime() )
4✔
1090
    return getEditorWidgetSetup( field, QStringLiteral( "DateTime" ) );
×
1091
  else if ( field.type() == QVariant::Bool )
4✔
1092
    return getEditorWidgetSetup( field, QStringLiteral( "CheckBox" ) );
×
1093
  else
1094
    return getEditorWidgetSetup( field, QStringLiteral( "TextEdit" ) );
12✔
1095
}
1096

1097
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field, const QString &widgetType, const QVariantMap &additionalArgs )
9✔
1098
{
1099
  if ( field.name() == QStringLiteral( "fid" ) )
9✔
1100
    return QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() );
4✔
1101

1102
  if ( widgetType.isEmpty() )
7✔
1103
  {
1104
    return QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() );
×
1105
  }
1106
  else
1107
  {
1108
    QMultiMap<QString, QVariant> config;
7✔
1109
    config = config.unite( QMultiMap( additionalArgs ) );
7✔
1110

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

1142
    QVariantMap cfg;
7✔
1143
    QList<QString> keys = config.uniqueKeys();
7✔
1144
    for ( int i = 0; i < keys.size(); i++ )
28✔
1145
    {
1146
      cfg.insert( keys.at( i ), config.value( keys.at( i ) ) );
21✔
1147
    }
1148

1149
    return QgsEditorWidgetSetup( widgetType, cfg );
7✔
1150
  }
7✔
1151
}
1152

1153
QString InputUtils::geometryFromLayer( QgsVectorLayer *layer )
11✔
1154
{
1155
  if ( layer )
11✔
1156
  {
1157
    switch ( layer->geometryType() )
11✔
1158
    {
1159
      case Qgis::GeometryType::Point: return QStringLiteral( "point" );
20✔
1160
      case Qgis::GeometryType::Line: return QStringLiteral( "linestring" );
2✔
1161
      case Qgis::GeometryType::Polygon: return QStringLiteral( "polygon" );
×
1162
      case Qgis::GeometryType::Null: return QStringLiteral( "nullGeo" );
×
1163
      default: return QString();
×
1164
    }
1165
  }
1166
  return QString();
×
1167
}
1168

1169
bool InputUtils::isPointLayer( QgsVectorLayer *layer )
×
1170
{
1171
  return geometryFromLayer( layer ) == "point";
×
1172
}
1173

1174
bool InputUtils::isLineLayer( QgsVectorLayer *layer )
×
1175
{
1176
  return geometryFromLayer( layer ) == "linestring";
×
1177
}
1178

1179
bool InputUtils::isPolygonLayer( QgsVectorLayer *layer )
×
1180
{
1181
  return geometryFromLayer( layer ) == "polygon";
×
1182
}
1183

1184
bool InputUtils::isNoGeometryLayer( QgsVectorLayer *layer )
×
1185
{
1186
  return geometryFromLayer( layer ) == "nullGeo";
×
1187
}
1188

1189
bool InputUtils::isMultiPartLayer( QgsVectorLayer *layer )
×
1190
{
1191
  if ( !layer )
×
1192
  {
1193
    return false;
×
1194
  }
1195
  return QgsWkbTypes::isMultiType( layer->wkbType() );
×
1196
}
1197

1198
bool InputUtils::isSpatialLayer( QgsVectorLayer *layer )
×
1199
{
1200
  if ( !layer )
×
1201
  {
1202
    return false;
×
1203
  }
1204
  return layer->isSpatial();
×
1205
}
1206

1207
qreal InputUtils::calculateScreenDpr()
1✔
1208
{
1209
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1210

1211
  if ( !screens.isEmpty() )
1✔
1212
  {
1213
    QScreen *screen = screens.at( 0 );
1✔
1214
    double dpiX = screen->physicalDotsPerInchX();
1✔
1215
    double dpiY = screen->physicalDotsPerInchY();
1✔
1216

1217
    qreal realDpi = dpiX < dpiY ? dpiX : dpiY;
1✔
1218
    realDpi = realDpi * screen->devicePixelRatio();
1✔
1219

1220
    return realDpi / 160.;
1✔
1221
  }
1222

1223
  return 1;
×
1224
}
1✔
1225

1226
qreal InputUtils::calculateDpRatio()
1✔
1227
{
1228
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1229

1230
  if ( !screens.isEmpty() )
1✔
1231
  {
1232
    QScreen *screen = screens.at( 0 );
1✔
1233

1234
    qreal realDpr = calculateScreenDpr();
1✔
1235
    return realDpr / screen->devicePixelRatio();
1✔
1236
  }
1237

1238
  return 1;
×
1239
}
1✔
1240

1241
bool InputUtils::equals( const QPointF &a, const QPointF &b, double epsilon )
8✔
1242
{
1243
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
8✔
1244
}
1245

1246
bool InputUtils::equals( const QgsPointXY &a, const QgsPointXY &b, double epsilon )
42✔
1247
{
1248
  if ( a.isEmpty() && b.isEmpty() )
42✔
1249
    return true;
3✔
1250
  if ( a.isEmpty() != b.isEmpty() )
39✔
1251
    return false;
4✔
1252

1253
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
35✔
1254
}
1255

1256
bool InputUtils::equals( const QgsPoint &a, const QgsPoint &b, double epsilon )
316✔
1257
{
1258
  if ( a.isEmpty() && b.isEmpty() )
316✔
1259
    return true;
35✔
1260
  if ( a.isEmpty() != b.isEmpty() )
281✔
1261
    return false;
2✔
1262

1263
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
279✔
1264
}
1265

1266
QString InputUtils::formatPoint(
1✔
1267
  const QgsPoint &point,
1268
  QgsCoordinateFormatter::Format format,
1269
  int decimals,
1270
  QgsCoordinateFormatter::FormatFlags flags )
1271
{
1272
  return QgsCoordinateFormatter::format( point, format, decimals, flags );
1✔
1273
}
1274

1275
QString InputUtils::formatDistance( double distance,
10✔
1276
                                    Qgis::DistanceUnit units,
1277
                                    int decimals,
1278
                                    Qgis::SystemOfMeasurement destSystem )
1279
{
1280
  double destDistance;
1281
  Qgis::DistanceUnit destUnits;
1282

1283
  humanReadableDistance( distance, units, destSystem, destDistance, destUnits );
10✔
1284

1285
  return QStringLiteral( "%1 %2" )
20✔
1286
         .arg( QString::number( destDistance, 'f', decimals ) )
20✔
1287
         .arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
30✔
1288
}
1289

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

1312
void InputUtils::formatToMetricDistance( double srcDistance,
11✔
1313
    Qgis::DistanceUnit srcUnits,
1314
    double &destDistance,
1315
    Qgis::DistanceUnit &destUnits )
1316
{
1317
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Millimeters );
11✔
1318
  if ( dist < 0 )
11✔
1319
  {
1320
    destDistance = 0;
1✔
1321
    destUnits = Qgis::DistanceUnit::Millimeters;
1✔
1322
    return;
1✔
1323
  }
1324

1325
  double mmToKm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Kilometers, Qgis::DistanceUnit::Millimeters );
10✔
1326
  if ( dist > mmToKm )
10✔
1327
  {
1328
    destDistance = dist / mmToKm;
5✔
1329
    destUnits = Qgis::DistanceUnit::Kilometers;
5✔
1330
    return;
5✔
1331
  }
1332

1333
  double mmToM = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Meters, Qgis::DistanceUnit::Millimeters );
5✔
1334
  if ( dist > mmToM )
5✔
1335
  {
1336
    destDistance = dist / mmToM;
4✔
1337
    destUnits = Qgis::DistanceUnit::Meters;
4✔
1338
    return;
4✔
1339
  }
1340

1341
  double mmToCm = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Centimeters, Qgis::DistanceUnit::Millimeters );
1✔
1342
  if ( dist > mmToCm )
1✔
1343
  {
1344
    destDistance = dist / mmToCm;
1✔
1345
    destUnits = Qgis::DistanceUnit::Centimeters;
1✔
1346
    return;
1✔
1347
  }
1348

1349
  destDistance = dist;
×
1350
  destUnits = Qgis::DistanceUnit::Millimeters;
×
1351
}
1352

1353
void InputUtils::formatToImperialDistance( double srcDistance,
2✔
1354
    Qgis::DistanceUnit srcUnits,
1355
    double &destDistance,
1356
    Qgis::DistanceUnit &destUnits )
1357
{
1358
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
2✔
1359
  if ( dist < 0 )
2✔
1360
  {
1361
    destDistance = 0;
×
1362
    destUnits = Qgis::DistanceUnit::Feet;
×
1363
    return;
×
1364
  }
1365

1366
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Miles, Qgis::DistanceUnit::Feet );
2✔
1367
  if ( dist > feetToMile )
2✔
1368
  {
1369
    destDistance = dist / feetToMile;
1✔
1370
    destUnits = Qgis::DistanceUnit::Miles;
1✔
1371
    return;
1✔
1372
  }
1373

1374
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
1✔
1375
  if ( dist > feetToYard )
1✔
1376
  {
1377
    destDistance = dist / feetToYard;
1✔
1378
    destUnits = Qgis::DistanceUnit::Yards;
1✔
1379
    return;
1✔
1380
  }
1381

1382
  destDistance = dist;
×
1383
  destUnits = Qgis::DistanceUnit::Feet;
×
1384
  return;
×
1385
}
1386

1387
void InputUtils::formatToUSCSDistance( double srcDistance,
1✔
1388
                                       Qgis::DistanceUnit srcUnits,
1389
                                       double &destDistance,
1390
                                       Qgis::DistanceUnit &destUnits )
1391
{
1392
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, Qgis::DistanceUnit::Feet );
1✔
1393
  if ( dist < 0 )
1✔
1394
  {
1395
    destDistance = 0;
×
1396
    destUnits = Qgis::DistanceUnit::Feet;
×
1397
    return;
×
1398
  }
1399

1400
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::NauticalMiles, Qgis::DistanceUnit::Feet );
1✔
1401
  if ( dist > feetToMile )
1✔
1402
  {
1403
    destDistance = dist / feetToMile;
1✔
1404
    destUnits = Qgis::DistanceUnit::NauticalMiles;
1✔
1405
    return;
1✔
1406
  }
1407

1408
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( Qgis::DistanceUnit::Yards, Qgis::DistanceUnit::Feet );
×
1409
  if ( dist > feetToYard )
×
1410
  {
1411
    destDistance = dist / feetToYard;
×
1412
    destUnits = Qgis::DistanceUnit::Yards;
×
1413
    return;
×
1414
  }
1415

1416
  destDistance = dist;
×
1417
  destUnits = Qgis::DistanceUnit::Feet;
×
1418
  return;
×
1419
}
1420

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

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

1450
QString InputUtils::evaluateExpression( const FeatureLayerPair &pair, QgsProject *activeProject, const QString &expression )
1✔
1451
{
1452
  QList<QgsExpressionContextScope *> scopes;
1✔
1453
  scopes << QgsExpressionContextUtils::globalScope();
1✔
1454
  scopes << QgsExpressionContextUtils::projectScope( activeProject );
1✔
1455
  scopes << QgsExpressionContextUtils::layerScope( pair.layer() );
1✔
1456

1457
  QgsExpressionContext context( scopes );
1✔
1458
  context.setFeature( pair.feature() );
1✔
1459
  QgsExpression expr( expression );
1✔
1460
  return expr.evaluate( &context ).toString();
3✔
1461
}
1✔
1462

1463
QString InputUtils::fieldType( const QgsField &field )
×
1464
{
1465
  return QVariant( field.type() ).typeName();
×
1466
}
1467

1468
QString InputUtils::dateTimeFieldFormat( const QString &fieldFormat )
×
1469
{
1470
  if ( QgsDateTimeFieldFormatter::DATE_FORMAT == fieldFormat )
×
1471
  {
1472
    return QString( "Date" );
×
1473
  }
1474
  else if ( QgsDateTimeFieldFormatter::TIME_FORMAT == fieldFormat )
×
1475
  {
1476
    return QString( "Time" );
×
1477
  }
1478
  // cppcheck-suppress duplicateBranch
1479
  else if ( QgsDateTimeFieldFormatter::DATETIME_FORMAT == fieldFormat )
×
1480
  {
1481
    return QString( "Date Time" );
×
1482
  }
1483
  else
1484
  {
1485
    return QString( "Date Time" );
×
1486
  }
1487
}
1488

1489
bool InputUtils::isFeatureIdValid( qint64 featureId )
2✔
1490
{
1491
  return !FID_IS_NEW( featureId ) && !FID_IS_NULL( featureId );
2✔
1492
}
1493

1494
QgsRectangle InputUtils::stakeoutPathExtent(
5✔
1495
  MapPosition *mapPosition,
1496
  const FeatureLayerPair &targetFeature,
1497
  InputMapSettings *mapSettings,
1498
  double mapExtentOffset
1499
)
1500
{
1501
  if ( !mapPosition || !mapSettings || !targetFeature.isValid() )
5✔
1502
    return QgsRectangle();
×
1503

1504
  QgsRectangle extent = mapSettings->extent();
5✔
1505

1506
  // We currently support only point geometries
1507
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
5✔
1508
    return extent;
×
1509

1510
  if ( !mapPosition->positionKit() || !mapPosition->mapSettings() )
5✔
1511
    return extent;
×
1512

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

1520
  QgsPoint gpsPointRaw = mapPosition->positionKit()->positionCoordinate();
5✔
1521

1522
  qreal distance = distanceBetweenGpsAndFeature( gpsPointRaw, targetFeature, mapSettings );
5✔
1523
  qreal scale = distanceToScale( distance );
5✔
1524
  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✔
1525

1526
  if ( mapExtentOffset > 0 )
5✔
1527
  {
1528
    panelOffset = mapExtentOffset / 2.0;
×
1529
  }
1530

1531
  if ( distance <= 1 )
5✔
1532
  {
1533
    // center to target point
1534
    QgsPoint targetPointRaw( extractPointFromFeature( targetFeature ) );
1✔
1535
    QgsPointXY targetPointInMapCRS = transformPoint(
2✔
1536
                                       targetFeature.layer()->crs(),
2✔
1537
                                       mapSettings->destinationCrs(),
2✔
1538
                                       mapSettings->transformContext(),
2✔
1539
                                       targetPointRaw
1540
                                     );
1✔
1541

1542
    if ( targetPointInMapCRS.isEmpty() )
1✔
1543
    {
1544
      // unsuccessful transform
1545
      return extent;
×
1546
    }
1547

1548
    QgsPointXY targetPointInCanvasXY = mapSettings->coordinateToScreen( QgsPoint( targetPointInMapCRS ) );
1✔
1549
    QgsPointXY centerInCanvasXY( targetPointInCanvasXY.x(), targetPointInCanvasXY.y() + panelOffset );
1✔
1550
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
1✔
1551

1552
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
1✔
1553
  }
1✔
1554
  else
1555
  {
1556
    // center to GPS position
1557
    QgsPointXY gpsPointInCanvasXY = mapPosition->screenPosition();
4✔
1558
    QgsPointXY centerInCanvasXY( gpsPointInCanvasXY.x(), gpsPointInCanvasXY.y() + panelOffset );
4✔
1559
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
4✔
1560

1561
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
4✔
1562
  }
1563

1564
  return extent;
5✔
1565
}
5✔
1566

1567
QgsGeometry InputUtils::stakeoutGeometry( const QgsPoint &mapPosition, const FeatureLayerPair &target, InputMapSettings *mapSettings )
×
1568
{
1569
  if ( !mapSettings || !target.isValid() )
×
1570
    return QgsGeometry();
×
1571

1572
  QgsPointXY targetInLayerCoordinates = target.feature().geometry().asPoint();
×
1573
  QgsPointXY t = transformPointXY( target.layer()->crs(), mapSettings->destinationCrs(), mapSettings->transformContext(), targetInLayerCoordinates );
×
1574

1575
  QVector<QgsPoint> points { mapPosition, QgsPoint( t ) };
×
1576

1577
  return QgsGeometry::fromPolyline( points );
×
1578
}
×
1579

1580
qreal InputUtils::distanceToScale( qreal distance )
29✔
1581
{
1582
  // Stakeout extent scale is computed based on these (empirically found) conditions:
1583
  //   - if distance is > 10m, use 1:205 scale (~ 5m on mobile)
1584
  //   - if distance is 3-10m, use 1:105 scale (~ 2m on mobile)
1585
  //   - if distance is 1-3m,  use 1:55 scale  (~ 1m on mobile)
1586
  //   - if distance is < 1m,  use 1:25 scale  (~ 0.5m on mobile)
1587

1588
  qreal scale = 205;
29✔
1589

1590
  if ( distance <= 1 )
29✔
1591
  {
1592
    scale = 25;
9✔
1593
  }
1594
  else if ( distance <= 3 && distance > 1 )
20✔
1595
  {
1596
    scale = 55;
7✔
1597
  }
1598
  else if ( distance <= 10 && distance > 3 )
13✔
1599
  {
1600
    scale = 105;
5✔
1601
  }
1602

1603
  return scale;
29✔
1604
}
1605

1606
qreal InputUtils::distanceBetweenGpsAndFeature( QgsPoint gpsPosition, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
6✔
1607
{
1608
  if ( !mapSettings || !targetFeature.isValid() )
6✔
1609
    return -1;
×
1610

1611
  // We calculate distance only between points
1612
  if ( targetFeature.layer()->geometryType() != Qgis::GeometryType::Point )
6✔
1613
    return -1;
×
1614

1615
  // Transform gps position to map CRS
1616
  QgsPointXY transformedPosition = transformPoint(
12✔
1617
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
12✔
1618
                                     mapSettings->destinationCrs(),
12✔
1619
                                     mapSettings->transformContext(),
12✔
1620
                                     gpsPosition
1621
                                   );
6✔
1622

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

1628
  // Transform target point to map CRS
1629
  QgsPoint target( extractPointFromFeature( targetFeature ) );
6✔
1630
  QgsPointXY transformedTarget = transformPoint(
12✔
1631
                                   targetFeature.layer()->crs(),
12✔
1632
                                   mapSettings->destinationCrs(),
12✔
1633
                                   mapSettings->transformContext(),
12✔
1634
                                   target
1635
                                 );
6✔
1636

1637
  if ( transformedTarget.isEmpty() )
6✔
1638
  {
1639
    return -1;
×
1640
  }
1641

1642
  QgsDistanceArea distanceArea;
6✔
1643
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
6✔
1644

1645
  qreal distance = distanceArea.measureLine( transformedPosition, transformedTarget );
6✔
1646
  distance = distanceArea.convertLengthMeasurement( distance, Qgis::DistanceUnit::Meters );
6✔
1647

1648
  return distance;
6✔
1649
}
6✔
1650

1651
qreal InputUtils::angleBetweenGpsAndFeature( QgsPoint gpsPoint, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
1✔
1652
{
1653
  if ( !mapSettings || !targetFeature.isValid() )
1✔
1654
    return -1;
×
1655

1656
  QgsVectorLayer *layer = targetFeature.layer();
1✔
1657
  QgsFeature f = targetFeature.feature();
1✔
1658

1659
  // Only points are supported
1660
  if ( layer->geometryType() != Qgis::GeometryType::Point )
1✔
1661
    return -1;
×
1662

1663
  // Transform gps position to map CRS
1664
  QgsPointXY transformedPosition = transformPoint(
2✔
1665
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
2✔
1666
                                     mapSettings->destinationCrs(),
2✔
1667
                                     mapSettings->transformContext(),
2✔
1668
                                     gpsPoint
1669
                                   );
1✔
1670

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

1676
  // Transform target point to map CRS
1677
  QgsPoint target( extractPointFromFeature( targetFeature ) );
1✔
1678
  QgsPointXY transformedTarget = transformPoint(
2✔
1679
                                   targetFeature.layer()->crs(),
2✔
1680
                                   mapSettings->destinationCrs(),
2✔
1681
                                   mapSettings->transformContext(),
2✔
1682
                                   target
1683
                                 );
1✔
1684

1685
  if ( transformedTarget.isEmpty() )
1✔
1686
  {
1687
    return -1;
×
1688
  }
1689

1690
  QgsDistanceArea distanceArea;
1✔
1691
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
1✔
1692

1693
  return distanceArea.bearing( transformedPosition, transformedTarget );
1✔
1694
}
1✔
1695

1696
QString InputUtils::featureTitle( const FeatureLayerPair &pair, QgsProject *project )
×
1697
{
1698
  if ( !project || !pair.isValid() )
×
1699
    return QString();
×
1700

1701
  QString title;
×
1702

1703
  QgsVectorLayer *layer = pair.layer();
×
1704

1705
  // can't use QgsExpressionContextUtils::globalProjectLayerScopes() because it uses QgsProject::instance()
1706
  QList<QgsExpressionContextScope *> scopes;
×
1707
  scopes << QgsExpressionContextUtils::globalScope();
×
1708
  scopes << QgsExpressionContextUtils::projectScope( project );
×
1709
  scopes << QgsExpressionContextUtils::layerScope( layer );
×
1710

1711
  QgsExpressionContext context( scopes );
×
1712
  context.setFeature( pair.feature() );
×
1713
  QgsExpression expr( pair.layer()->displayExpression() );
×
1714
  title = expr.evaluate( &context ).toString();
×
1715

1716
  if ( title.isEmpty() )
×
1717
    title = QStringLiteral( "Feature %1" ).arg( pair.feature().id() );
×
1718

1719
  return title;
×
1720
}
×
1721

1722
FeatureLayerPair InputUtils::createFeatureLayerPair(
1✔
1723
  QgsVectorLayer *layer,
1724
  const QgsGeometry &geometry,
1725
  VariablesManager *variablesmanager,
1726
  QgsExpressionContextScope *additionalScope )
1727
{
1728
  if ( !layer )
1✔
1729
    return FeatureLayerPair();
×
1730

1731
  QgsAttributes attrs( layer->fields().count() );
1✔
1732
  QgsExpressionContext context = layer->createExpressionContext();
1✔
1733

1734
  if ( variablesmanager )
1✔
1735
    context << variablesmanager->positionScope();
×
1736

1737
  if ( additionalScope )
1✔
1738
    context << additionalScope;
1✔
1739

1740
  QgsFeature feat = QgsVectorLayerUtils::createFeature( layer, geometry, attrs.toMap(), &context );
1✔
1741
  return FeatureLayerPair( feat, layer );
1✔
1742
}
1✔
1743

1744
void InputUtils::createEditBuffer( QgsVectorLayer *layer )
×
1745
{
1746
  if ( layer )
×
1747
  {
1748
    if ( !layer->editBuffer() )
×
1749
    {
1750
      layer->startEditing();
×
1751
    }
1752
  }
1753
}
×
1754

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

1774
QgsPointXY InputUtils::extractPointFromFeature( const FeatureLayerPair &feature )
11✔
1775
{
1776
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
11✔
1777
    return QgsPointXY();
2✔
1778

1779
  QgsFeature f = feature.feature();
9✔
1780
  const QgsAbstractGeometry *g = f.geometry().constGet();
9✔
1781

1782
  return QgsPoint( dynamic_cast< const QgsPoint * >( g )->toQPointF() );
18✔
1783
}
9✔
1784

1785
bool InputUtils::isPointLayerFeature( const FeatureLayerPair &feature )
4✔
1786
{
1787
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
4✔
1788
    return false;
3✔
1789
  const QgsAbstractGeometry *g = feature.feature().geometry().constGet();
1✔
1790
  const QgsPoint *point = dynamic_cast< const QgsPoint * >( g );
1✔
1791
  return point != nullptr;
1✔
1792
}
1793

1794
void InputUtils::zoomToProject( QgsProject *qgsProject, InputMapSettings *mapSettings )
×
1795
{
1796
  if ( !qgsProject || !mapSettings )
×
1797
  {
1798
    qDebug() << "Cannot zoom to extent, MapSettings or QgsProject is not defined";
×
1799
    return;
×
1800
  }
1801
  QgsRectangle extent;
×
1802

1803
  QgsProjectViewSettings *viewSettings = qgsProject->viewSettings();
×
1804
  extent = viewSettings->presetFullExtent();
×
1805
  if ( extent.isNull() )
×
1806
  {
1807
    bool hasWMS;
1808
    QStringList WMSExtent = qgsProject->readListEntry( "WMSExtent", QStringLiteral( "/" ), QStringList(), &hasWMS );
×
1809

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

1825
  if ( extent.isEmpty() )
×
1826
  {
1827
    extent.grow( qgsProject->crs().isGeographic() ? 0.01 : 1000.0 );
×
1828
  }
1829
  extent.scale( 1.05 );
×
1830
  mapSettings->setExtent( extent );
×
1831
}
1832

1833
QString InputUtils::loadIconFromLayer( QgsMapLayer *layer )
18✔
1834
{
1835
  if ( !layer )
18✔
1836
    return QString();
×
1837

1838
  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
18✔
1839

1840
  if ( vectorLayer )
18✔
1841
  {
1842
    Qgis::GeometryType geometry = vectorLayer->geometryType();
17✔
1843
    return iconFromGeometry( geometry );
17✔
1844
  }
1845
  else
1846
    return QString( "qrc:/mIconRasterLayer.svg" );
1✔
1847
}
1848

1849
QString InputUtils::loadIconFromFeature( QgsFeature feature )
4✔
1850
{
1851
  return iconFromGeometry( feature.geometry().type() );
8✔
1852
}
1853

1854
QString InputUtils::iconFromGeometry( const Qgis::GeometryType &geometry )
21✔
1855
{
1856
  switch ( geometry )
21✔
1857
  {
1858
    case Qgis::GeometryType::Point: return QString( "qrc:/mIconPointLayer.svg" );
6✔
1859
    case Qgis::GeometryType::Line: return QString( "qrc:/mIconLineLayer.svg" );
6✔
1860
    case Qgis::GeometryType::Polygon: return QString( "qrc:/mIconPolygonLayer.svg" );
6✔
1861
    default: return QString( "qrc:/mIconTableLayer.svg" );
3✔
1862
  }
1863
}
1864

1865
bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
×
1866
{
1867
  int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
×
1868
  return ImageUtils::rescale( path, quality );
×
1869
}
1870

1871
QgsGeometry InputUtils::createGeometryForLayer( QgsVectorLayer *layer )
36✔
1872
{
1873
  QgsGeometry geometry;
36✔
1874

1875
  if ( !layer )
36✔
1876
  {
1877
    return geometry;
×
1878
  }
1879

1880
  bool isMulti = QgsWkbTypes::isMultiType( layer->wkbType() );
36✔
1881

1882
  switch ( layer->geometryType() )
36✔
1883
  {
1884
    case Qgis::GeometryType::Point:
15✔
1885
    {
1886
      if ( isMulti )
15✔
1887
      {
1888
        QgsMultiPoint *multiPoint = new QgsMultiPoint();
3✔
1889
        geometry.set( multiPoint );
3✔
1890
      }
1891
      else
1892
      {
1893
        QgsPoint *point = new QgsPoint();
12✔
1894
        geometry.set( point );
12✔
1895
      }
1896
      break;
15✔
1897
    }
1898

1899
    case Qgis::GeometryType::Line:
10✔
1900
    {
1901
      if ( isMulti )
10✔
1902
      {
1903
        QgsMultiLineString *multiLine = new QgsMultiLineString();
4✔
1904
        geometry.set( multiLine );
4✔
1905
      }
1906
      else
1907
      {
1908
        QgsLineString *line = new QgsLineString();
6✔
1909
        geometry.set( line );
6✔
1910
      }
1911
      break;
10✔
1912
    }
1913

1914
    case Qgis::GeometryType::Polygon:
11✔
1915
    {
1916
      if ( isMulti )
11✔
1917
      {
1918
        QgsLineString *line = new QgsLineString();
2✔
1919
        QgsPolygon *polygon = new QgsPolygon( line );
2✔
1920
        QgsMultiPolygon *multiPolygon = new QgsMultiPolygon();
2✔
1921
        multiPolygon->addGeometry( polygon );
2✔
1922
        geometry.set( multiPolygon );
2✔
1923
      }
1924
      else
1925
      {
1926
        QgsLineString *line = new QgsLineString();
9✔
1927
        QgsPolygon *polygon = new QgsPolygon( line );
9✔
1928
        geometry.set( polygon );
9✔
1929
      }
1930
      break;
11✔
1931
    }
1932

1933
    default:
×
1934
      break;
×
1935
  }
1936

1937
  if ( QgsWkbTypes::hasZ( layer->wkbType() ) )
36✔
1938
  {
1939
    geometry.get()->addZValue( 0 );
12✔
1940
  }
1941

1942
  if ( QgsWkbTypes::hasM( layer->wkbType() ) )
36✔
1943
  {
1944
    geometry.get()->addMValue( 0 );
7✔
1945
  }
1946

1947
  return geometry;
36✔
1948
}
×
1949

1950
QString InputUtils::invalidGeometryWarning( QgsVectorLayer *layer )
6✔
1951
{
1952
  QString msg;
6✔
1953
  if ( !layer )
6✔
1954
  {
1955
    return msg;
×
1956
  }
1957

1958
  int nPoints = 1;
6✔
1959
  if ( layer->geometryType() == Qgis::GeometryType::Line )
6✔
1960
  {
1961
    nPoints = 2;
2✔
1962
  }
1963
  else if ( layer->geometryType() == Qgis::GeometryType::Polygon )
4✔
1964
  {
1965
    nPoints = 3;
2✔
1966
  }
1967

1968
  if ( QgsWkbTypes::isMultiType( layer->wkbType() ) )
6✔
1969
  {
1970
    return tr( "You need to add at least %1 point(s) to every part." ).arg( nPoints );
3✔
1971
  }
1972
  else
1973
  {
1974
    return tr( "You need to add at least %1 point(s)." ).arg( nPoints );
3✔
1975
  }
1976
}
6✔
1977

1978
void InputUtils::updateFeature( const FeatureLayerPair &pair )
×
1979
{
1980
  if ( !pair.layer() )
×
1981
  {
1982
    return;
×
1983
  }
1984

1985
  if ( !pair.feature().isValid() )
×
1986
  {
1987
    return;
×
1988
  }
1989

1990
  if ( !pair.layer()->isEditable() )
×
1991
  {
1992
    pair.layer()->startEditing();
×
1993
  }
1994

1995
  QgsFeature f( pair.feature() );
×
1996
  pair.layer()->updateFeature( f );
×
1997
  pair.layer()->commitChanges();
×
1998
  pair.layer()->triggerRepaint();
×
1999
}
×
2000

2001
QString InputUtils::imageGalleryLocation()
×
2002
{
2003
  QStringList galleryPaths = QStandardPaths::standardLocations( QStandardPaths::PicturesLocation );
×
2004

2005
  if ( galleryPaths.isEmpty() )
×
2006
  {
2007
    CoreUtils::log( QStringLiteral( "Image Picker" ), QStringLiteral( "Could not find standard path to image gallery" ) );
×
2008
    return QString();
×
2009
  }
2010

2011
  return galleryPaths.last();
×
2012
}
×
2013

2014
QString InputUtils::layerAttribution( QgsMapLayer *layer )
2✔
2015
{
2016
  if ( !layer || !layer->isValid() )
2✔
2017
  {
2018
    return QString();
×
2019
  }
2020

2021
  QStringList rights = layer->metadata().rights();
2✔
2022
  if ( !rights.isEmpty() )
2✔
2023
  {
2024
    return rights.join( QStringLiteral( ", " ) );
2✔
2025
  }
2026

2027
  return QString();
1✔
2028
}
2✔
2029

2030
const double PROFILER_THRESHOLD = 0.001;
2031
static double qgsRuntimeProfilerExtractModelAsText( QStringList &lines, const QString &group, const QModelIndex &parent, int level )
×
2032
{
2033
  double total_elapsed = 0.0;
×
2034

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

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

2057
QVector<QString> InputUtils::qgisProfilerLog()
×
2058
{
2059
  QVector<QString> lines;
×
2060
  const QString project = QgsProject::instance()->fileName();
×
2061

2062
  if ( !project.isEmpty() )
×
2063
  {
2064
    lines << QStringLiteral( "QgsProject filename: %1" ).arg( project );
×
2065
  }
2066

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

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

2083
QList<QgsPoint> InputUtils::parsePositionUpdates( const QString &data )
8✔
2084
{
2085
  QList<QgsPoint> parsedUpdates;
8✔
2086
  QStringList positions = data.split( '\n', Qt::SkipEmptyParts );
8✔
2087

2088
  if ( positions.isEmpty() )
8✔
2089
  {
2090
    return parsedUpdates;
2✔
2091
  }
2092

2093
  for ( int ix = 0; ix < positions.size(); ix++ )
14✔
2094
  {
2095
    QStringList coordinates = positions[ix].split( ' ', Qt::SkipEmptyParts );
8✔
2096

2097
    if ( coordinates.size() != 4 )
8✔
2098
    {
2099
      continue;
4✔
2100
    }
2101

2102
    QgsPoint geop;
4✔
2103
    geop.setX( coordinates[0].toDouble() ); // long
4✔
2104
    geop.setY( coordinates[1].toDouble() ); // lat
4✔
2105
    geop.setZ( coordinates[2].toDouble() ); // alt
4✔
2106
    geop.setM( coordinates[3].toDouble() ); // UTC time in secs
4✔
2107
    parsedUpdates << geop;
4✔
2108
  }
8✔
2109

2110
  return parsedUpdates;
6✔
2111
}
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