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

MerginMaps / input / 6300324651

25 Sep 2023 01:49PM UTC coverage: 62.063% (+0.1%) from 61.953%
6300324651

Pull #2805

github

PeterPetrik
fix crash with broken relations
Pull Request #2805: fix crash with broken relations

7576 of 12207 relevant lines covered (62.06%)

102.55 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

99
  return QFile::copy( modSrcPath, dstPath );
3✔
100
}
3✔
101

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

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

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

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

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

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

162
QString InputUtils::formatDateTimeDiff( const QDateTime &tMin, const QDateTime &tMax )
15✔
163
{
164
  qint64 daysDiff = tMin.daysTo( tMax );
15✔
165

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

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

220
  return INVALID_DATETIME_STR;
221
}
222

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

293
  return numerator / denominator;
×
294
}
×
295

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

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

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

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

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

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

338
  return g;
1✔
339
}
1✔
340

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

464
  QVector<double> data;
×
465

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

481
  return data;
×
482
}
×
483

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

674
  return QString();
×
675
}
676

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

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

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

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

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

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

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

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

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

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

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

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

768
  return QgsPointXY();
×
769
}
770

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

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

805
  return QgsPoint();
×
806
}
807

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1193
  return 1;
×
1194
}
1✔
1195

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

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

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

1208
  return 1;
×
1209
}
1✔
1210

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1534
  return extent;
5✔
1535
}
5✔
1536

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

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

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

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

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

1558
  qreal scale = 205;
29✔
1559

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

1573
  return scale;
29✔
1574
}
1575

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

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

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

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

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

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

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

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

1618
  return distance;
6✔
1619
}
6✔
1620

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

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

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

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

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

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

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

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

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

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

1671
  QString title;
×
1672

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

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

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

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

1689
  return title;
×
1690
}
×
1691

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1841

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

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

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

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

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

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

1904
    default:
×
1905
      break;
×
1906
  }
1907

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

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

1918
  return geometry;
36✔
1919
}
×
1920

1921

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

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

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

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

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

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

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

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

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

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

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

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

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

2002
QList<QgsPoint> InputUtils::parsePositionUpdates( const QString &data )
8✔
2003
{
2004
  QList<QgsPoint> parsedUpdates;
8✔
2005
  QStringList positions = data.split( '\n', Qt::SkipEmptyParts );
8✔
2006

2007
  if ( positions.isEmpty() )
8✔
2008
  {
2009
    return parsedUpdates;
2✔
2010
  }
2011

2012
  for ( int ix = 0; ix < positions.size(); ix++ )
14✔
2013
  {
2014
    QStringList coordinates = positions[ix].split( ' ', Qt::SkipEmptyParts );
8✔
2015

2016
    if ( coordinates.size() != 4 )
8✔
2017
    {
2018
      continue;
4✔
2019
    }
2020

2021
    QgsPoint geop;
4✔
2022
    geop.setX( coordinates[0].toDouble() ); // long
4✔
2023
    geop.setY( coordinates[1].toDouble() ); // lat
4✔
2024
    geop.setZ( coordinates[2].toDouble() ); // alt
4✔
2025
    geop.setM( coordinates[3].toDouble() ); // UTC time in secs
4✔
2026
    parsedUpdates << geop;
4✔
2027
  }
8✔
2028

2029
  return parsedUpdates;
6✔
2030
}
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