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

MerginMaps / input / 5222002891

pending completion
5222002891

push

github

web-flow
fix autotests (#2710)

for testing API we need PRO subscription; still few purchasing tests are skipped

8109 of 13061 relevant lines covered (62.09%)

107.91 hits per line

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

53.3
/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" );
17✔
62
static const QString INVALID_DATETIME_STR = QStringLiteral( "Invalid datetime" );
17✔
63

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

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

75
bool InputUtils::removeFile( const QString &filePath )
7✔
76
{
77
  QFile file( filePath );
7✔
78
  return file.remove( filePath );
7✔
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 )
3✔
103
{
104
  QDir dir;
3✔
105
  return dir.mkpath( path );
3✔
106
}
3✔
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
}
15✔
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::convertGeometryToMapCRS( const QgsGeometry &geometry, QgsVectorLayer *sourceLayer, InputMapSettings *targetSettings )
×
321
{
322
  QgsGeometry g( geometry );
×
323

324
  if ( !sourceLayer || !targetSettings )
×
325
  {
326
    return QgsGeometry();
×
327
  }
328

329
  QgsCoordinateTransform ct( sourceLayer->crs(), targetSettings->destinationCrs(), targetSettings->transformContext() );
×
330
  if ( !ct.isShortCircuited() )
×
331
  {
332
    try
333
    {
334
      g.transform( ct );
×
335
    }
×
336
    catch ( QgsCsException &e )
337
    {
338
      Q_UNUSED( e )
×
339
      return QgsGeometry();
×
340
    }
×
341
  }
×
342

343
  return g;
×
344
}
×
345

346
QgsGeometry InputUtils::extractGeometry( const FeatureLayerPair &pair )
×
347
{
348
  if ( !pair.isValid() )
×
349
    return QgsGeometry();
×
350

351
  return pair.feature().geometry();
×
352
}
×
353

354
static void addLineString( const QgsLineString *line, QVector<double> &data )
×
355
{
356
  data << line->numPoints();
×
357
  const double *x = line->xData();
×
358
  const double *y = line->yData();
×
359
  for ( int i = 0; i < line->numPoints(); ++i )
×
360
  {
361
    data << x[i] << y[i];
×
362
  }
×
363
}
×
364

365
static void addSingleGeometry( const QgsAbstractGeometry *geom, QgsWkbTypes::GeometryType type, QVector<double> &data )
×
366
{
367
  switch ( type )
×
368
  {
369
    case QgsWkbTypes::PointGeometry:
370
    {
371
      const QgsPoint *point = qgsgeometry_cast<const QgsPoint *>( geom );
×
372
      if ( point )
×
373
      {
374
        data << 0 << point->x() << point->y();
×
375
      }
×
376
      break;
×
377
    }
378

379
    case QgsWkbTypes::LineGeometry:
380
    {
381
      const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( geom );
×
382
      if ( line )
×
383
      {
384
        data << 1;
×
385
        addLineString( line, data );
×
386
      }
×
387
      break;
×
388
    }
389

390
    case QgsWkbTypes::PolygonGeometry:
391
    {
392
      const QgsPolygon *poly = qgsgeometry_cast<const QgsPolygon *>( geom );
×
393
      if ( poly )
×
394
      {
395
        if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->exteriorRing() ) )
×
396
        {
397
          data << 2;
×
398
          addLineString( line, data );
×
399
        }
×
400
        for ( int i = 0; i < poly->numInteriorRings(); ++i )
×
401
        {
402
          if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( poly->interiorRing( i ) ) )
×
403
          {
404
            data << 2;
×
405
            addLineString( line, data );
×
406
          }
×
407
        }
×
408
      }
×
409
      break;
×
410
    }
411

412
    case QgsWkbTypes::UnknownGeometry:
413
    case QgsWkbTypes::NullGeometry:
414
      break;
×
415
  }
416
}
×
417

418
QVector<double> InputUtils::extractGeometryCoordinates( const QgsGeometry &geometry )
×
419
{
420
  if ( geometry.isNull() )
×
421
    return QVector<double>();
×
422

423
  QVector<double> data;
×
424

425
  const QgsAbstractGeometry *geom = geometry.constGet();
×
426
  QgsWkbTypes::GeometryType geomType = geometry.type();
×
427
  const QgsGeometryCollection *collection = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
×
428
  if ( collection && !collection->isEmpty() )
×
429
  {
430
    for ( int i = 0; i < collection->numGeometries(); ++i )
×
431
    {
432
      addSingleGeometry( collection->geometryN( i ), geomType, data );
×
433
    }
×
434
  }
×
435
  else
436
  {
437
    addSingleGeometry( geom, geomType, data );
×
438
  }
439

440
  return data;
×
441
}
×
442

443
QString InputUtils::filesToString( QList<MerginFile> files )
×
444
{
445
  QStringList resultList;
×
446
  for ( MerginFile file : files )
×
447
  {
448
    resultList << file.path;
×
449
  }
×
450
  return resultList.join( ", " );
×
451
}
×
452

453
QString InputUtils::bytesToHumanSize( double bytes )
6✔
454
{
455
  const int precision = 1;
6✔
456
  if ( bytes < 1e-5 )
6✔
457
  {
458
    return "0.0";
×
459
  }
460
  else if ( bytes < 1024.0 * 1024.0 )
6✔
461
  {
462
    return QString::number( bytes / 1024.0, 'f', precision ) + " KB";
×
463
  }
464
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 )
6✔
465
  {
466
    return QString::number( bytes / 1024.0 / 1024.0, 'f', precision ) + " MB";
×
467
  }
468
  else if ( bytes < 1024.0 * 1024.0 * 1024.0 * 1024.0 )
6✔
469
  {
470
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " GB";
6✔
471
  }
472
  else
473
  {
474
    return QString::number( bytes / 1024.0 / 1024.0 / 1024.0 / 1024.0, 'f', precision ) + " TB";
×
475
  }
476
}
6✔
477

478
bool InputUtils::acquireCameraPermission()
×
479
{
480
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
481
  {
482
    return mAndroidUtils->requestCameraPermission();
×
483
  }
484
  return true;
×
485
}
×
486

487
bool InputUtils::isBluetoothTurnedOn()
×
488
{
489
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
490
  {
491
    return mAndroidUtils->isBluetoothTurnedOn();
×
492
  }
493
  return true;
×
494
}
×
495

496
void InputUtils::turnBluetoothOn()
×
497
{
498
  if ( appPlatform() == QStringLiteral( "android" ) && mAndroidUtils )
×
499
  {
500
    mAndroidUtils->turnBluetoothOn();
×
501
  }
×
502
}
×
503

504
void InputUtils::quitApp()
×
505
{
506
  if ( appPlatform() == QStringLiteral( "android" ) )
×
507
  {
508
    AndroidUtils::quitApp();
×
509
  }
×
510
  else
511
  {
512
    QCoreApplication::quit();
×
513
  }
514
}
×
515

516
QLocale InputUtils::fixLocaleCountry( QLocale applocale )
29✔
517
{
518
  QLocale out = applocale;
29✔
519

520
  if ( applocale.language() == QLocale::English )
29✔
521
  {
522
    out = QLocale( QLocale::English, QLocale::AnyCountry );
22✔
523
  }
22✔
524
  else if ( applocale.language() == QLocale::German || applocale.language() == QLocale::LowGerman || applocale.language() == QLocale::SwissGerman )
7✔
525
  {
526
    out = QLocale( QLocale::German, QLocale::AnyCountry );
×
527
  }
×
528
  else if ( applocale.language() == QLocale::French )
7✔
529
  {
530
    out = QLocale( QLocale::French, QLocale::AnyCountry );
3✔
531
  }
3✔
532
  else if ( applocale.language() == QLocale::Italian )
4✔
533
  {
534
    out = QLocale( QLocale::Italian, QLocale::AnyCountry );
×
535
  }
×
536
  else if ( applocale.language() == QLocale::Spanish )
4✔
537
  {
538
    out = QLocale( QLocale::Spanish, QLocale::AnyCountry );
2✔
539
  }
2✔
540
  else if ( applocale.language() == QLocale::Turkish )
2✔
541
  {
542
    out = QLocale( QLocale::Turkish, QLocale::AnyCountry );
×
543
  }
×
544
  else if ( applocale.language() == QLocale::Polish )
2✔
545
  {
546
    out = QLocale( QLocale::Polish, QLocale::AnyCountry );
9✔
547
  }
1✔
548
  else if ( applocale.language() == QLocale::Slovak )
1✔
549
  {
550
    out = QLocale( QLocale::Slovak, QLocale::AnyCountry );
1✔
551
  }
1✔
552
  else if ( applocale.language() == QLocale::Croatian )
×
553
  {
554
    out = QLocale( QLocale::Croatian, QLocale::AnyCountry );
×
555
  }
×
556

557
  CoreUtils::log( QStringLiteral( "Locale" ), QStringLiteral( "Converting %1 locale to simple %2" ).arg( applocale.name(), out.name() ) );
29✔
558

559
  return out;
29✔
560
}
29✔
561

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

580
bool InputUtils::isMobilePlatform()
2✔
581
{
582
  QString platform = appPlatform();
2✔
583
  return platform == QStringLiteral( "android" ) || platform == QStringLiteral( "ios" );
2✔
584
}
2✔
585

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

601
  CoreUtils::log( "QGIS " + tag, levelStr + ": " + message );
17✔
602
}
17✔
603

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

614
  QDir srcDir( srcPath );
56✔
615
  const QFileInfoList fileInfoList = srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden );
56✔
616
  foreach ( const QFileInfo &info, fileInfoList )
542✔
617
  {
618
    QString fileName = info.fileName();
486✔
619

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

628
    QString srcItemPath = srcPath + "/" + fileName;
486✔
629
    QString dstItemPath = dstPath + "/" + fileName;
486✔
630

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

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

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

676
    if ( QFile::rename( srcPath, newPath ) ) return newPath;
×
677
  }
×
678

679
  return QString();
×
680
}
×
681

682
void InputUtils::showNotification( const QString &message )
×
683
{
684
  emit showNotificationRequested( message );
×
685
}
×
686

687
double InputUtils::ratherZeroThanNaN( double d )
×
688
{
689
  return ( isnan( d ) ) ? 0.0 : d;
×
690
}
691

692
/**
693
 * Makes QgsCoordinateReferenceSystem::fromEpsgId accessible for QML components
694
 */
695
QgsCoordinateReferenceSystem InputUtils::coordinateReferenceSystemFromEpsgId( long epsg )
12✔
696
{
697
  return QgsCoordinateReferenceSystem::fromEpsgId( epsg );
12✔
698
}
699

700
QgsPointXY InputUtils::pointXY( double x, double y )
1✔
701
{
702
  return QgsPointXY( x, y );
1✔
703
}
704

705
QgsPoint InputUtils::point( double x, double y, double z, double m )
2✔
706
{
707
  return QgsPoint( x, y, z, m );
2✔
708
}
709

710
QgsGeometry InputUtils::emptyGeometry()
×
711
{
712
  return QgsGeometry();
×
713
}
714

715
QgsFeature InputUtils::emptyFeature()
×
716
{
717
  return QgsFeature();
×
718
}
719

720
bool InputUtils::isEmptyGeometry( const QgsGeometry &geometry )
×
721
{
722
  return geometry.isEmpty();
×
723
}
724

725
QgsPoint InputUtils::coordinateToPoint( const QGeoCoordinate &coor )
×
726
{
727
  return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() );
×
728
}
729

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

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

763
  return QgsPointXY();
×
764
}
27✔
765

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

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

800
  return QgsPoint();
×
801
}
28✔
802

803
QPointF InputUtils::transformPointToScreenCoordinates( const QgsCoordinateReferenceSystem &srcCrs, InputMapSettings *mapSettings, const QgsPoint &srcPoint )
×
804
{
805
  if ( !mapSettings || srcPoint.isEmpty() )
×
806
    return QPointF();
×
807

808
  QgsPoint mapcrsPoint = transformPoint( srcCrs, mapSettings->destinationCrs(), mapSettings->transformContext(), srcPoint );
×
809
  return mapSettings->coordinateToScreen( mapcrsPoint );
×
810
}
×
811

812
double InputUtils::screenUnitsToMeters( InputMapSettings *mapSettings, int baseLengthPixels )
30✔
813
{
814
  if ( mapSettings == nullptr ) return 0.0;
30✔
815

816
  QgsDistanceArea mDistanceArea;
30✔
817
  mDistanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
30✔
818
  mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
30✔
819

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

829
QgsPoint InputUtils::mapPointToGps( QPointF mapPosition, InputMapSettings *mapSettings )
5✔
830
{
831
  if ( !mapSettings )
5✔
832
    return QgsPoint();
×
833

834
  if ( mapPosition.isNull() )
5✔
835
    return QgsPoint();
×
836

837
  QgsPoint positionMapCrs = mapSettings->screenToCoordinate( mapPosition );
5✔
838
  QgsCoordinateReferenceSystem crsGPS = coordinateReferenceSystemFromEpsgId( 4326 );
5✔
839

840
  const QgsPointXY transformedXY = transformPoint(
5✔
841
                                     mapSettings->destinationCrs(),
5✔
842
                                     crsGPS,
843
                                     QgsCoordinateTransformContext(),
5✔
844
                                     positionMapCrs
845
                                   );
846

847
  if ( transformedXY.isEmpty() )
5✔
848
  {
849
    // point could not be transformed
850
    return QgsPoint();
1✔
851
  }
852

853
  return QgsPoint( transformedXY );
4✔
854
}
5✔
855

856
bool InputUtils::fileExists( const QString &path )
8✔
857
{
858
  QFileInfo check_file( path );
8✔
859
  // check if file exists and if yes: Is it really a file and no directory?
860
  return ( check_file.exists() && check_file.isFile() );
8✔
861
}
8✔
862

863
QString InputUtils::resolveTargetDir( const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
6✔
864
{
865
  QString expression;
6✔
866
  QMap<QString, QVariant> collection = config.value( QStringLiteral( "PropertyCollection" ) ).toMap();
6✔
867
  QMap<QString, QVariant> props = collection.value( QStringLiteral( "properties" ) ).toMap();
6✔
868

869
  if ( !props.isEmpty() )
6✔
870
  {
871
    QMap<QString, QVariant> propertyRootPath = props.value( QStringLiteral( "propertyRootPath" ) ).toMap();
1✔
872
    expression = propertyRootPath.value( QStringLiteral( "expression" ), QString() ).toString();
1✔
873
  }
1✔
874

875
  if ( !expression.isEmpty() )
6✔
876
  {
877
    return evaluateExpression( pair, activeProject, expression );
1✔
878
  }
879
  else
880
  {
881
    QString defaultRoot = config.value( QStringLiteral( "DefaultRoot" ) ).toString();
5✔
882
    if ( defaultRoot.isEmpty() )
5✔
883
    {
884
      return homePath;
4✔
885
    }
886
    else
887
    {
888
      return defaultRoot;
1✔
889
    }
890
  }
5✔
891
}
6✔
892

893
QString InputUtils::resolvePrefixForRelativePath( int relativeStorageMode, const QString &homePath, const QString &targetDir )
3✔
894
{
895
  if ( relativeStorageMode == 1 )
3✔
896
  {
897
    return homePath;
1✔
898
  }
899
  else if ( relativeStorageMode == 2 )
2✔
900
  {
901
    return targetDir;
1✔
902
  }
903
  else
904
  {
905
    return QString();
1✔
906
  }
907
}
3✔
908

909
QString InputUtils::getAbsolutePath( const QString &path, const QString &prefixPath )
3✔
910
{
911
  return ( prefixPath.isEmpty() ) ? path : QStringLiteral( "%1/%2" ).arg( prefixPath ).arg( path );
3✔
912
}
×
913

914
QString InputUtils::resolvePath( const QString &path, const QString &homePath, const QVariantMap &config, const FeatureLayerPair &pair, QgsProject *activeProject )
3✔
915
{
916
  int relativeStorageMode = config.value( QStringLiteral( "RelativeStorage" ) ).toInt();
3✔
917
  QString targetDir = resolveTargetDir( homePath, config, pair, activeProject );
3✔
918
  QString prefixToRelativePath = resolvePrefixForRelativePath( relativeStorageMode, homePath, targetDir );
3✔
919

920
  return getAbsolutePath( path, prefixToRelativePath );
3✔
921
}
3✔
922

923
QString InputUtils::getRelativePath( const QString &path, const QString &prefixPath )
5✔
924
{
925
  QString modPath = path;
5✔
926
  QString filePrefix( "file://" );
5✔
927

928
  if ( path.startsWith( filePrefix ) )
5✔
929
  {
930
    modPath = modPath.replace( filePrefix, QString() );
1✔
931
  }
1✔
932

933
  if ( prefixPath.isEmpty() ) return modPath;
5✔
934

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

950
    if ( prefixCanonicalPath.length() > 1 && canonicalPath.startsWith( prefixCanonicalPath ) )
×
951
    {
952
      return canonicalPath.replace( prefixCanonicalPath, QString() );
×
953
    }
954
  }
×
955

956
  return QString();
1✔
957
}
5✔
958

959
void InputUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
×
960
{
961
  QgsMessageLog::logMessage( message, tag, level );
×
962
}
×
963

964
void InputUtils::log( const QString &context, const QString &message )
×
965
{
966
  CoreUtils::log( context, message );
×
967
}
×
968

969
const QUrl InputUtils::getThemeIcon( const QString &name )
1✔
970
{
971
  QString path = QStringLiteral( "qrc:/%1.svg" ).arg( name );
1✔
972
  QgsDebugMsg( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ) );
1✔
973
  return QUrl( path );
1✔
974
}
1✔
975

976
const QUrl InputUtils::getEditorComponentSource( const QString &widgetName, const QVariantMap &config, const QgsField &field )
2✔
977
{
978
  QString path( "../editor/input%1.qml" );
2✔
979

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

996
  if ( field.name().contains( "qrcode", Qt::CaseInsensitive ) || field.alias().contains( "qrcode", Qt::CaseInsensitive ) )
2✔
997
  {
998
    return QUrl( path.arg( QStringLiteral( "qrcodereader" ) ) );
×
999
  }
1000

1001
  if ( widgetName == QStringLiteral( "textedit" ) )
2✔
1002
  {
1003
    if ( config.value( "IsMultiline" ).toBool() )
×
1004
    {
1005
      return QUrl( path.arg( QStringLiteral( "texteditmultiline" ) ) );
×
1006
    }
1007
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
×
1008
  }
1009

1010
  if ( widgetName == QStringLiteral( "valuerelation" ) )
2✔
1011
  {
1012
    const QgsMapLayer *referencedLayer = QgsProject::instance()->mapLayer( config.value( "Layer" ).toString() );
×
1013
    const QgsVectorLayer *layer = qobject_cast<const QgsVectorLayer *>( referencedLayer );
×
1014

1015
    if ( layer )
×
1016
    {
1017
      int featuresCount = layer->dataProvider()->featureCount();
×
1018
      if ( featuresCount > 4 )
×
1019
        return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1020
    }
×
1021

1022
    if ( config.value( "AllowMulti" ).toBool() )
×
1023
    {
1024
      return QUrl( path.arg( QLatin1String( "valuerelationpage" ) ) );
×
1025
    }
1026

1027
    return QUrl( path.arg( QLatin1String( "valuerelationcombobox" ) ) );
×
1028
  }
1029

1030
  QStringList supportedWidgets = { QStringLiteral( "textedit" ),
18✔
1031
                                   QStringLiteral( "valuemap" ),
2✔
1032
                                   QStringLiteral( "valuerelation" ),
2✔
1033
                                   QStringLiteral( "checkbox" ),
2✔
1034
                                   QStringLiteral( "externalresource" ),
2✔
1035
                                   QStringLiteral( "datetime" ),
2✔
1036
                                   QStringLiteral( "range" ),
2✔
1037
                                   QStringLiteral( "relation" ),
2✔
1038
                                   QStringLiteral( "relationreference" )
2✔
1039
                                 };
1040
  if ( supportedWidgets.contains( widgetName ) )
2✔
1041
  {
1042
    return QUrl( path.arg( widgetName ) );
1✔
1043
  }
1044
  else
17✔
1045
  {
1046
    return QUrl( path.arg( QLatin1String( "textedit" ) ) );
18✔
1047
  }
1048
}
19✔
1049

1050
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field )
24✔
1051
{
1052
  if ( field.isNumeric() )
24✔
1053
    return getEditorWidgetSetup( field, QStringLiteral( "Range" ) );
4✔
1054
  else if ( field.isDateOrTime() )
3✔
1055
    return getEditorWidgetSetup( field, QStringLiteral( "DateTime" ) );
×
1056
  else if ( field.type() == QVariant::Bool )
3✔
1057
    return getEditorWidgetSetup( field, QStringLiteral( "CheckBox" ) );
×
1058
  else
1059
    return getEditorWidgetSetup( field, QStringLiteral( "TextEdit" ) );
3✔
1060
}
7✔
1061

1062
const QgsEditorWidgetSetup InputUtils::getEditorWidgetSetup( const QgsField &field, const QString &widgetType, const QVariantMap &additionalArgs )
8✔
1063
{
1064
  if ( field.name() == QStringLiteral( "fid" ) )
8✔
1065
    return QgsEditorWidgetSetup( QStringLiteral( "Hidden" ), QVariantMap() );
2✔
1066

1067
  if ( widgetType.isEmpty() )
6✔
1068
  {
1069
    return QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() );
×
1070
  }
1071
  else
1072
  {
1073
    QMultiMap<QString, QVariant> config;
6✔
1074
    config = config.unite( QMultiMap( additionalArgs ) );
6✔
1075

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

1107
    QVariantMap cfg;
6✔
1108
    QList<QString> keys = config.uniqueKeys();
6✔
1109
    for ( int i = 0; i < keys.size(); i++ )
25✔
1110
    {
1111
      cfg.insert( keys.at( i ), config.value( keys.at( i ) ) );
19✔
1112
    }
19✔
1113

1114
    return QgsEditorWidgetSetup( widgetType, cfg );
6✔
1115
  }
6✔
1116
}
8✔
1117

1118
QString InputUtils::geometryFromLayer( QgsVectorLayer *layer )
11✔
1119
{
1120
  if ( layer )
11✔
1121
  {
1122
    switch ( layer->geometryType() )
11✔
1123
    {
1124
      case QgsWkbTypes::PointGeometry: return QStringLiteral( "point" );
10✔
1125
      case QgsWkbTypes::LineGeometry: return QStringLiteral( "linestring" );
1✔
1126
      case QgsWkbTypes::PolygonGeometry: return QStringLiteral( "polygon" );
×
1127
      case QgsWkbTypes::NullGeometry: return QStringLiteral( "nullGeo" );
×
1128
      default: return QString();
×
1129
    }
1130
  }
1131
  return QString();
×
1132
}
11✔
1133

1134
bool InputUtils::isPointLayer( QgsVectorLayer *layer )
×
1135
{
1136
  return geometryFromLayer( layer ) == "point";
×
1137
}
×
1138

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

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

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

1154
bool InputUtils::isMultiPartLayer( QgsVectorLayer *layer )
×
1155
{
1156
  if ( !layer )
×
1157
  {
1158
    return false;
×
1159
  }
1160
  return QgsWkbTypes::isMultiType( layer->wkbType() );
×
1161
}
×
1162

1163
bool InputUtils::isSpatialLayer( QgsVectorLayer *layer )
×
1164
{
1165
  if ( !layer )
×
1166
  {
1167
    return false;
×
1168
  }
1169
  return layer->isSpatial();
×
1170
}
×
1171

1172
qreal InputUtils::calculateScreenDpr()
1✔
1173
{
1174
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1175

1176
  if ( !screens.isEmpty() )
1✔
1177
  {
1178
    QScreen *screen = screens.at( 0 );
1✔
1179
    double dpiX = screen->physicalDotsPerInchX();
1✔
1180
    double dpiY = screen->physicalDotsPerInchY();
1✔
1181

1182
    qreal realDpi = dpiX < dpiY ? dpiX : dpiY;
1✔
1183
    realDpi = realDpi * screen->devicePixelRatio();
1✔
1184

1185
    return realDpi / 160.;
1✔
1186
  }
1187

1188
  return 1;
×
1189
}
1✔
1190

1191
qreal InputUtils::calculateDpRatio()
1✔
1192
{
1193
  const QList<QScreen *> screens = QGuiApplication::screens();
1✔
1194

1195
  if ( !screens.isEmpty() )
1✔
1196
  {
1197
    QScreen *screen = screens.at( 0 );
1✔
1198

1199
    qreal realDpr = calculateScreenDpr();
1✔
1200
    return realDpr / screen->devicePixelRatio();
1✔
1201
  }
1202

1203
  return 1;
×
1204
}
1✔
1205

1206
bool InputUtils::equals( const QPointF &a, const QPointF &b, double epsilon )
8✔
1207
{
1208
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
8✔
1209
}
1210

1211
bool InputUtils::equals( const QgsPointXY &a, const QgsPointXY &b, double epsilon )
42✔
1212
{
1213
  if ( a.isEmpty() && b.isEmpty() )
42✔
1214
    return true;
3✔
1215
  if ( a.isEmpty() != b.isEmpty() )
39✔
1216
    return false;
4✔
1217

1218
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
35✔
1219
}
42✔
1220

1221
bool InputUtils::equals( const QgsPoint &a, const QgsPoint &b, double epsilon )
312✔
1222
{
1223
  if ( a.isEmpty() && b.isEmpty() )
312✔
1224
    return true;
35✔
1225
  if ( a.isEmpty() != b.isEmpty() )
277✔
1226
    return false;
2✔
1227

1228
  return qgsDoubleNear( a.x(), b.x(), epsilon ) && qgsDoubleNear( a.y(), b.y(), epsilon );
275✔
1229
}
312✔
1230

1231
QString InputUtils::formatPoint(
1✔
1232
  const QgsPoint &point,
1233
  QgsCoordinateFormatter::Format format,
1234
  int decimals,
1235
  QgsCoordinateFormatter::FormatFlags flags )
1236
{
1237
  return QgsCoordinateFormatter::format( point, format, decimals, flags );
1✔
1238
}
1239

1240
QString InputUtils::formatDistance( double distance,
10✔
1241
                                    QgsUnitTypes::DistanceUnit units,
1242
                                    int decimals,
1243
                                    QgsUnitTypes::SystemOfMeasurement destSystem )
1244
{
1245
  double destDistance;
1246
  QgsUnitTypes::DistanceUnit destUnits;
1247

1248
  humanReadableDistance( distance, units, destSystem, destDistance, destUnits );
10✔
1249

1250
  return QStringLiteral( "%1 %2" )
20✔
1251
         .arg( QString::number( destDistance, 'f', decimals ) )
10✔
1252
         .arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
10✔
1253
}
×
1254

1255
void InputUtils::humanReadableDistance( double srcDistance, QgsUnitTypes::DistanceUnit srcUnits,
14✔
1256
                                        QgsUnitTypes::SystemOfMeasurement destSystem,
1257
                                        double &destDistance, QgsUnitTypes::DistanceUnit &destUnits )
1258
{
1259
  if ( ( destSystem == QgsUnitTypes::MetricSystem ) || ( destSystem == QgsUnitTypes::UnknownSystem ) )
14✔
1260
  {
1261
    return formatToMetricDistance( srcDistance, srcUnits, destDistance, destUnits );
11✔
1262
  }
1263
  else if ( destSystem == QgsUnitTypes::ImperialSystem )
3✔
1264
  {
1265
    return formatToImperialDistance( srcDistance, srcUnits, destDistance, destUnits );
2✔
1266
  }
1267
  else if ( destSystem == QgsUnitTypes::USCSSystem )
1✔
1268
  {
1269
    return formatToUSCSDistance( srcDistance, srcUnits, destDistance, destUnits );
1✔
1270
  }
1271
  else
1272
  {
1273
    Q_ASSERT( false ); //should never happen
×
1274
  }
1275
}
14✔
1276

1277
void InputUtils::formatToMetricDistance( double srcDistance,
11✔
1278
    QgsUnitTypes::DistanceUnit srcUnits,
1279
    double &destDistance,
1280
    QgsUnitTypes::DistanceUnit &destUnits )
1281
{
1282
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, QgsUnitTypes::DistanceMillimeters );
11✔
1283
  if ( dist < 0 )
11✔
1284
  {
1285
    destDistance = 0;
1✔
1286
    destUnits = QgsUnitTypes::DistanceMillimeters;
1✔
1287
    return;
1✔
1288
  }
1289

1290
  double mmToKm = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceKilometers, QgsUnitTypes::DistanceMillimeters );
10✔
1291
  if ( dist > mmToKm )
10✔
1292
  {
1293
    destDistance = dist / mmToKm;
5✔
1294
    destUnits = QgsUnitTypes::DistanceKilometers;
5✔
1295
    return;
5✔
1296
  }
1297

1298
  double mmToM = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceMeters, QgsUnitTypes::DistanceMillimeters );
5✔
1299
  if ( dist > mmToM )
5✔
1300
  {
1301
    destDistance = dist / mmToM;
4✔
1302
    destUnits = QgsUnitTypes::DistanceMeters;
4✔
1303
    return;
4✔
1304
  }
1305

1306
  double mmToCm = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceCentimeters, QgsUnitTypes::DistanceMillimeters );
1✔
1307
  if ( dist > mmToCm )
1✔
1308
  {
1309
    destDistance = dist / mmToCm;
1✔
1310
    destUnits = QgsUnitTypes::DistanceCentimeters;
1✔
1311
    return;
1✔
1312
  }
1313

1314
  destDistance = dist;
×
1315
  destUnits = QgsUnitTypes::DistanceMillimeters;
×
1316
}
11✔
1317

1318
void InputUtils::formatToImperialDistance( double srcDistance,
2✔
1319
    QgsUnitTypes::DistanceUnit srcUnits,
1320
    double &destDistance,
1321
    QgsUnitTypes::DistanceUnit &destUnits )
1322
{
1323
  double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, QgsUnitTypes::DistanceFeet );
2✔
1324
  if ( dist < 0 )
2✔
1325
  {
1326
    destDistance = 0;
×
1327
    destUnits = QgsUnitTypes::DistanceFeet;
×
1328
    return;
×
1329
  }
1330

1331
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceMiles, QgsUnitTypes::DistanceFeet );
2✔
1332
  if ( dist > feetToMile )
2✔
1333
  {
1334
    destDistance = dist / feetToMile;
1✔
1335
    destUnits = QgsUnitTypes::DistanceMiles;
1✔
1336
    return;
1✔
1337
  }
1338

1339
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceYards, QgsUnitTypes::DistanceFeet );
1✔
1340
  if ( dist > feetToYard )
1✔
1341
  {
1342
    destDistance = dist / feetToYard;
1✔
1343
    destUnits = QgsUnitTypes::DistanceYards;
1✔
1344
    return;
1✔
1345
  }
1346

1347
  destDistance = dist;
×
1348
  destUnits = QgsUnitTypes::DistanceFeet;
×
1349
  return;
×
1350
}
2✔
1351

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

1365
  double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceNauticalMiles, QgsUnitTypes::DistanceFeet );
1✔
1366
  if ( dist > feetToMile )
1✔
1367
  {
1368
    destDistance = dist / feetToMile;
1✔
1369
    destUnits = QgsUnitTypes::DistanceNauticalMiles;
1✔
1370
    return;
1✔
1371
  }
1372

1373
  double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceYards, QgsUnitTypes::DistanceFeet );
×
1374
  if ( dist > feetToYard )
×
1375
  {
1376
    destDistance = dist / feetToYard;
×
1377
    destUnits = QgsUnitTypes::DistanceYards;
×
1378
    return;
×
1379
  }
1380

1381
  destDistance = dist;
×
1382
  destUnits = QgsUnitTypes::DistanceFeet;
×
1383
  return;
×
1384
}
1✔
1385

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

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

1415
QString InputUtils::evaluateExpression( const FeatureLayerPair &pair, QgsProject *activeProject, const QString &expression )
1✔
1416
{
1417
  QList<QgsExpressionContextScope *> scopes;
1✔
1418
  scopes << QgsExpressionContextUtils::globalScope();
1✔
1419
  scopes << QgsExpressionContextUtils::projectScope( activeProject );
1✔
1420
  scopes << QgsExpressionContextUtils::layerScope( pair.layer() );
1✔
1421

1422
  QgsExpressionContext context( scopes );
1✔
1423
  context.setFeature( pair.feature() );
1✔
1424
  QgsExpression expr( expression );
1✔
1425
  return expr.evaluate( &context ).toString();
1✔
1426
}
1✔
1427

1428
QString InputUtils::fieldType( const QgsField &field )
×
1429
{
1430
  return QVariant( field.type() ).typeName();
×
1431
}
×
1432

1433
QString InputUtils::dateTimeFieldFormat( const QString &fieldFormat )
×
1434
{
1435
  if ( QgsDateTimeFieldFormatter::DATE_FORMAT == fieldFormat )
×
1436
  {
1437
    return QString( "Date" );
×
1438
  }
1439
  else if ( QgsDateTimeFieldFormatter::TIME_FORMAT == fieldFormat )
×
1440
  {
1441
    return QString( "Time" );
×
1442
  }
1443
  else if ( QgsDateTimeFieldFormatter::DATETIME_FORMAT == fieldFormat )
×
1444
  {
1445
    return QString( "Date Time" );
×
1446
  }
1447
  else
1448
  {
1449
    return QString( "Date Time" );
×
1450
  }
1451
}
×
1452

1453
bool InputUtils::isFeatureIdValid( qint64 featureId )
2✔
1454
{
1455
  return !FID_IS_NEW( featureId ) && !FID_IS_NULL( featureId );
2✔
1456
}
1457

1458
QgsRectangle InputUtils::stakeoutPathExtent(
5✔
1459
  MapPosition *mapPosition,
1460
  const FeatureLayerPair &targetFeature,
1461
  InputMapSettings *mapSettings,
1462
  double mapExtentOffset
1463
)
1464
{
1465
  if ( !mapPosition || !mapSettings || !targetFeature.isValid() )
5✔
1466
    return QgsRectangle();
×
1467

1468
  QgsRectangle extent = mapSettings->extent();
5✔
1469

1470
  // We currently support only point geometries
1471
  if ( targetFeature.layer()->geometryType() != QgsWkbTypes::PointGeometry )
5✔
1472
    return extent;
×
1473

1474
  if ( !mapPosition->positionKit() || !mapPosition->mapSettings() )
5✔
1475
    return extent;
×
1476

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

1484
  QgsPoint gpsPointRaw = mapPosition->positionKit()->positionCoordinate();
5✔
1485

1486
  qreal distance = distanceBetweenGpsAndFeature( gpsPointRaw, targetFeature, mapSettings );
5✔
1487
  qreal scale = distanceToScale( distance );
5✔
1488
  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✔
1489

1490
  if ( mapExtentOffset > 0 )
5✔
1491
  {
1492
    panelOffset = mapExtentOffset / 2.0;
×
1493
  }
×
1494

1495
  if ( distance <= 1 )
5✔
1496
  {
1497
    // center to target point
1498
    QgsPoint targetPointRaw( extractPointFromFeature( targetFeature ) );
1✔
1499
    QgsPointXY targetPointInMapCRS = transformPoint(
1✔
1500
                                       targetFeature.layer()->crs(),
1✔
1501
                                       mapSettings->destinationCrs(),
1✔
1502
                                       mapSettings->transformContext(),
1✔
1503
                                       targetPointRaw
1504
                                     );
1505

1506
    if ( targetPointInMapCRS.isEmpty() )
1✔
1507
    {
1508
      // unsuccessful transform
1509
      return extent;
×
1510
    }
1511

1512
    QgsPointXY targetPointInCanvasXY = mapSettings->coordinateToScreen( QgsPoint( targetPointInMapCRS ) );
1✔
1513
    QgsPointXY centerInCanvasXY( targetPointInCanvasXY.x(), targetPointInCanvasXY.y() + panelOffset );
1✔
1514
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
1✔
1515

1516
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
1✔
1517
  }
1✔
1518
  else
1519
  {
1520
    // center to GPS position
1521
    QgsPointXY gpsPointInCanvasXY = mapPosition->screenPosition();
4✔
1522
    QgsPointXY centerInCanvasXY( gpsPointInCanvasXY.x(), gpsPointInCanvasXY.y() + panelOffset );
4✔
1523
    QgsPointXY center = mapSettings->screenToCoordinate( centerInCanvasXY.toQPointF() );
4✔
1524

1525
    extent = mapSettings->mapSettings().computeExtentForScale( center, scale );
4✔
1526
  }
1527

1528
  return extent;
5✔
1529
}
5✔
1530

1531
QgsGeometry InputUtils::stakeoutGeometry( const QgsPoint &mapPosition, const FeatureLayerPair &target, InputMapSettings *mapSettings )
×
1532
{
1533
  if ( !mapSettings || !target.isValid() )
×
1534
    return QgsGeometry();
×
1535

1536
  QgsPointXY targetInLayerCoordinates = target.feature().geometry().asPoint();
×
1537
  QgsPointXY t = transformPointXY( target.layer()->crs(), mapSettings->destinationCrs(), mapSettings->transformContext(), targetInLayerCoordinates );
×
1538

1539
  QVector<QgsPoint> points { mapPosition, QgsPoint( t ) };
×
1540

1541
  return QgsGeometry::fromPolyline( points );
×
1542
}
×
1543

1544
qreal InputUtils::distanceToScale( qreal distance )
29✔
1545
{
1546
  // Stakeout extent scale is computed based on these (empirically found) conditions:
1547
  //   - if distance is > 10m, use 1:205 scale (~ 5m on mobile)
1548
  //   - if distance is 3-10m, use 1:105 scale (~ 2m on mobile)
1549
  //   - if distance is 1-3m,  use 1:55 scale  (~ 1m on mobile)
1550
  //   - if distance is < 1m,  use 1:25 scale  (~ 0.5m on mobile)
1551

1552
  qreal scale = 205;
29✔
1553

1554
  if ( distance <= 1 )
29✔
1555
  {
1556
    scale = 25;
9✔
1557
  }
9✔
1558
  else if ( distance <= 3 && distance > 1 )
20✔
1559
  {
1560
    scale = 55;
7✔
1561
  }
7✔
1562
  else if ( distance <= 10 && distance > 3 )
13✔
1563
  {
1564
    scale = 105;
5✔
1565
  }
5✔
1566

1567
  return scale;
29✔
1568
}
1569

1570
qreal InputUtils::distanceBetweenGpsAndFeature( QgsPoint gpsPosition, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
6✔
1571
{
1572
  if ( !mapSettings || !targetFeature.isValid() )
6✔
1573
    return -1;
×
1574

1575
  // We calculate distance only between points
1576
  if ( targetFeature.layer()->geometryType() != QgsWkbTypes::GeometryType::PointGeometry )
6✔
1577
    return -1;
×
1578

1579
  // Transform gps position to map CRS
1580
  QgsPointXY transformedPosition = transformPoint(
6✔
1581
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
6✔
1582
                                     mapSettings->destinationCrs(),
6✔
1583
                                     mapSettings->transformContext(),
6✔
1584
                                     gpsPosition
1585
                                   );
1586

1587
  if ( transformedPosition.isEmpty() )
6✔
1588
  {
1589
    return -1;
×
1590
  }
1591

1592
  // Transform target point to map CRS
1593
  QgsPoint target( extractPointFromFeature( targetFeature ) );
6✔
1594
  QgsPointXY transformedTarget = transformPoint(
6✔
1595
                                   targetFeature.layer()->crs(),
6✔
1596
                                   mapSettings->destinationCrs(),
6✔
1597
                                   mapSettings->transformContext(),
6✔
1598
                                   target
1599
                                 );
1600

1601
  if ( transformedTarget.isEmpty() )
6✔
1602
  {
1603
    return -1;
×
1604
  }
1605

1606
  QgsDistanceArea distanceArea;
6✔
1607
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
6✔
1608

1609
  qreal distance = distanceArea.measureLine( transformedPosition, transformedTarget );
6✔
1610
  distance = distanceArea.convertLengthMeasurement( distance, QgsUnitTypes::DistanceMeters );
6✔
1611

1612
  return distance;
6✔
1613
}
6✔
1614

1615
qreal InputUtils::angleBetweenGpsAndFeature( QgsPoint gpsPoint, const FeatureLayerPair &targetFeature, InputMapSettings *mapSettings )
1✔
1616
{
1617
  if ( !mapSettings || !targetFeature.isValid() )
1✔
1618
    return -1;
×
1619

1620
  QgsVectorLayer *layer = targetFeature.layer();
1✔
1621
  QgsFeature f = targetFeature.feature();
1✔
1622

1623
  // Only points are supported
1624
  if ( layer->geometryType() != QgsWkbTypes::GeometryType::PointGeometry )
1✔
1625
    return -1;
×
1626

1627
  // Transform gps position to map CRS
1628
  QgsPointXY transformedPosition = transformPoint(
1✔
1629
                                     coordinateReferenceSystemFromEpsgId( 4326 ),
1✔
1630
                                     mapSettings->destinationCrs(),
1✔
1631
                                     mapSettings->transformContext(),
1✔
1632
                                     gpsPoint
1633
                                   );
1634

1635
  if ( transformedPosition.isEmpty() )
1✔
1636
  {
1637
    return -1;
×
1638
  }
1639

1640
  // Transform target point to map CRS
1641
  QgsPoint target( extractPointFromFeature( targetFeature ) );
1✔
1642
  QgsPointXY transformedTarget = transformPoint(
1✔
1643
                                   targetFeature.layer()->crs(),
1✔
1644
                                   mapSettings->destinationCrs(),
1✔
1645
                                   mapSettings->transformContext(),
1✔
1646
                                   target
1647
                                 );
1648

1649
  if ( transformedTarget.isEmpty() )
1✔
1650
  {
1651
    return -1;
×
1652
  }
1653

1654
  QgsDistanceArea distanceArea;
1✔
1655
  distanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
1✔
1656

1657
  return distanceArea.bearing( transformedPosition, transformedTarget );
1✔
1658
}
1✔
1659

1660
QString InputUtils::featureTitle( const FeatureLayerPair &pair, QgsProject *project )
×
1661
{
1662
  if ( !project || !pair.isValid() )
×
1663
    return QString();
×
1664

1665
  QString title;
×
1666

1667
  QgsVectorLayer *layer = pair.layer();
×
1668

1669
  // can't use QgsExpressionContextUtils::globalProjectLayerScopes() because it uses QgsProject::instance()
1670
  QList<QgsExpressionContextScope *> scopes;
×
1671
  scopes << QgsExpressionContextUtils::globalScope();
×
1672
  scopes << QgsExpressionContextUtils::projectScope( project );
×
1673
  scopes << QgsExpressionContextUtils::layerScope( layer );
×
1674

1675
  QgsExpressionContext context( scopes );
×
1676
  context.setFeature( pair.feature() );
×
1677
  QgsExpression expr( pair.layer()->displayExpression() );
×
1678
  title = expr.evaluate( &context ).toString();
×
1679

1680
  if ( title.isEmpty() )
×
1681
    title = QStringLiteral( "Feature %1" ).arg( pair.feature().id() );
×
1682

1683
  return title;
×
1684
}
×
1685

1686
FeatureLayerPair InputUtils::createFeatureLayerPair( QgsVectorLayer *layer, const QgsGeometry &geometry, VariablesManager *variablesmanager )
×
1687
{
1688
  if ( !layer )
×
1689
    return FeatureLayerPair();
×
1690

1691
  QgsAttributes attrs( layer->fields().count() );
×
1692
  QgsExpressionContext context = layer->createExpressionContext();
×
1693

1694
  if ( variablesmanager )
×
1695
    context << variablesmanager->positionScope();
×
1696

1697
  QgsFeature feat = QgsVectorLayerUtils::createFeature( layer, geometry, attrs.toMap(), &context );
×
1698
  return FeatureLayerPair( feat, layer );
×
1699
}
×
1700

1701
void InputUtils::createEditBuffer( QgsVectorLayer *layer )
×
1702
{
1703
  if ( layer )
×
1704
  {
1705
    if ( !layer->editBuffer() )
×
1706
    {
1707
      layer->startEditing();
×
1708
    }
×
1709
  }
×
1710
}
×
1711

1712
FeatureLayerPair InputUtils::changeFeaturePairGeometry( FeatureLayerPair featurePair, const QgsGeometry &geometry )
×
1713
{
1714
  QgsVectorLayer *vlayer = featurePair.layer();
×
1715
  if ( vlayer )
×
1716
  {
1717
    InputUtils::createEditBuffer( vlayer );
×
1718
    QgsGeometry g( geometry );
×
1719
    vlayer->changeGeometry( featurePair.feature().id(), g );
×
1720
    vlayer->triggerRepaint();
×
1721
  }
×
1722

1723
  QgsFeature f = featurePair.layer()->getFeature( featurePair.feature().id() );
×
1724

1725
  return FeatureLayerPair( f, featurePair.layer() );
×
1726
}
×
1727

1728
QgsPointXY InputUtils::extractPointFromFeature( const FeatureLayerPair &feature )
11✔
1729
{
1730
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
11✔
1731
    return QgsPointXY();
2✔
1732

1733
  QgsFeature f = feature.feature();
9✔
1734
  const QgsAbstractGeometry *g = f.geometry().constGet();
9✔
1735

1736
  return QgsPoint( dynamic_cast< const QgsPoint * >( g )->toQPointF() );
9✔
1737
}
11✔
1738

1739
bool InputUtils::isPointLayerFeature( const FeatureLayerPair &feature )
4✔
1740
{
1741
  if ( !feature.isValid() || geometryFromLayer( feature.layer() ) != "point" )
4✔
1742
    return false;
3✔
1743
  const QgsAbstractGeometry *g = feature.feature().geometry().constGet();
1✔
1744
  const QgsPoint *point = dynamic_cast< const QgsPoint * >( g );
1✔
1745
  return point != nullptr;
1✔
1746
}
4✔
1747

1748
void InputUtils::zoomToProject( QgsProject *qgsProject, InputMapSettings *mapSettings )
×
1749
{
1750
  if ( !qgsProject || !mapSettings )
×
1751
  {
1752
    qDebug() << "Cannot zoom to extent, MapSettings or QgsProject is not defined";
×
1753
    return;
×
1754
  }
1755
  QgsRectangle extent;
×
1756

1757
  QgsProjectViewSettings *viewSettings = qgsProject->viewSettings();
×
1758
  extent = viewSettings->presetFullExtent();
×
1759
  if ( extent.isNull() )
×
1760
  {
1761
    bool hasWMS;
1762
    QStringList WMSExtent = qgsProject->readListEntry( "WMSExtent", QStringLiteral( "/" ), QStringList(), &hasWMS );
×
1763

1764
    if ( hasWMS && ( WMSExtent.length() == 4 ) )
×
1765
    {
1766
      extent.set( WMSExtent[0].toDouble(), WMSExtent[1].toDouble(), WMSExtent[2].toDouble(), WMSExtent[3].toDouble() );
×
1767
    }
×
1768
    else // set layers extent
1769
    {
1770
      const QVector<QgsMapLayer *> layers = qgsProject->layers<QgsMapLayer *>();
×
1771
      for ( const QgsMapLayer *layer : layers )
×
1772
      {
1773
        QgsRectangle layerExtent = mapSettings->mapSettings().layerExtentToOutputExtent( layer, layer->extent() );
×
1774
        extent.combineExtentWith( layerExtent );
×
1775
      }
1776
    }
×
1777
  }
×
1778

1779
  if ( extent.isEmpty() )
×
1780
  {
1781
    extent.grow( qgsProject->crs().isGeographic() ? 0.01 : 1000.0 );
×
1782
  }
×
1783
  extent.scale( 1.05 );
×
1784
  mapSettings->setExtent( extent );
×
1785
}
×
1786

1787
QString InputUtils::loadIconFromLayer( QgsMapLayer *layer )
18✔
1788
{
1789
  if ( !layer )
18✔
1790
    return QString();
×
1791

1792
  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
18✔
1793

1794
  if ( vectorLayer )
18✔
1795
  {
1796
    QgsWkbTypes::GeometryType geometry = vectorLayer->geometryType();
17✔
1797
    return iconFromGeometry( geometry );
17✔
1798
  }
1799
  else
1800
    return QString( "qrc:/mIconRasterLayer.svg" );
1✔
1801
}
18✔
1802

1803
QString InputUtils::loadIconFromFeature( QgsFeature feature )
4✔
1804
{
1805
  return iconFromGeometry( feature.geometry().type() );
4✔
1806
}
×
1807

1808
QString InputUtils::iconFromGeometry( const QgsWkbTypes::GeometryType &geometry )
21✔
1809
{
1810
  switch ( geometry )
21✔
1811
  {
1812
    case QgsWkbTypes::GeometryType::PointGeometry: return QString( "qrc:/mIconPointLayer.svg" );
6✔
1813
    case QgsWkbTypes::GeometryType::LineGeometry: return QString( "qrc:/mIconLineLayer.svg" );
6✔
1814
    case QgsWkbTypes::GeometryType::PolygonGeometry: return QString( "qrc:/mIconPolygonLayer.svg" );
6✔
1815
    default: return QString( "qrc:/mIconTableLayer.svg" );
3✔
1816
  }
1817
}
21✔
1818

1819
bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
×
1820
{
1821
  int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
×
1822
  return ImageUtils::rescale( path, quality );
×
1823
}
×
1824

1825

1826
QgsGeometry InputUtils::createGeometryForLayer( QgsVectorLayer *layer )
36✔
1827
{
1828
  QgsGeometry geometry;
36✔
1829

1830
  if ( !layer )
36✔
1831
  {
1832
    return geometry;
×
1833
  }
1834

1835
  bool isMulti = QgsWkbTypes::isMultiType( layer->wkbType() );
36✔
1836

1837
  switch ( layer->geometryType() )
36✔
1838
  {
1839
    case QgsWkbTypes::PointGeometry:
1840
    {
1841
      if ( isMulti )
15✔
1842
      {
1843
        QgsMultiPoint *multiPoint = new QgsMultiPoint();
3✔
1844
        geometry.set( multiPoint );
3✔
1845
      }
3✔
1846
      else
1847
      {
1848
        QgsPoint *point = new QgsPoint();
12✔
1849
        geometry.set( point );
12✔
1850
      }
1851
      break;
15✔
1852
    }
1853

1854
    case QgsWkbTypes::LineGeometry:
1855
    {
1856
      if ( isMulti )
10✔
1857
      {
1858
        QgsMultiLineString *multiLine = new QgsMultiLineString();
4✔
1859
        geometry.set( multiLine );
4✔
1860
      }
4✔
1861
      else
1862
      {
1863
        QgsLineString *line = new QgsLineString();
6✔
1864
        geometry.set( line );
6✔
1865
      }
1866
      break;
10✔
1867
    }
1868

1869
    case QgsWkbTypes::PolygonGeometry:
1870
    {
1871
      if ( isMulti )
11✔
1872
      {
1873
        QgsLineString *line = new QgsLineString();
2✔
1874
        QgsPolygon *polygon = new QgsPolygon( line );
2✔
1875
        QgsMultiPolygon *multiPolygon = new QgsMultiPolygon();
2✔
1876
        multiPolygon->addGeometry( polygon );
2✔
1877
        geometry.set( multiPolygon );
2✔
1878
      }
2✔
1879
      else
1880
      {
1881
        QgsLineString *line = new QgsLineString();
9✔
1882
        QgsPolygon *polygon = new QgsPolygon( line );
9✔
1883
        geometry.set( polygon );
9✔
1884
      }
1885
      break;
11✔
1886
    }
1887

1888
    default:
1889
      break;
×
1890
  }
1891

1892
  if ( QgsWkbTypes::hasZ( layer->wkbType() ) )
36✔
1893
  {
1894
    geometry.get()->addZValue( 0 );
12✔
1895
  }
12✔
1896

1897
  if ( QgsWkbTypes::hasM( layer->wkbType() ) )
36✔
1898
  {
1899
    geometry.get()->addMValue( 0 );
7✔
1900
  }
7✔
1901

1902
  return geometry;
36✔
1903
}
36✔
1904

1905

1906
QString InputUtils::invalidGeometryWarning( QgsVectorLayer *layer )
6✔
1907
{
1908
  QString msg;
6✔
1909
  if ( !layer )
6✔
1910
  {
1911
    return msg;
×
1912
  }
1913

1914
  int nPoints = 1;
6✔
1915
  if ( layer->geometryType() == QgsWkbTypes::LineGeometry )
6✔
1916
  {
1917
    nPoints = 2;
2✔
1918
  }
2✔
1919
  else if ( layer->geometryType() == QgsWkbTypes::PolygonGeometry )
4✔
1920
  {
1921
    nPoints = 3;
2✔
1922
  }
2✔
1923

1924
  if ( QgsWkbTypes::isMultiType( layer->wkbType() ) )
6✔
1925
  {
1926
    return tr( "You need to add at least %1 point(s) to every part." ).arg( nPoints );
3✔
1927
  }
1928
  else
1929
  {
1930
    return tr( "You need to add at least %1 point(s)." ).arg( nPoints );
3✔
1931
  }
1932
}
6✔
1933

1934
void InputUtils::updateFeature( const FeatureLayerPair &pair )
×
1935
{
1936
  if ( !pair.layer() )
×
1937
  {
1938
    return;
×
1939
  }
1940

1941
  if ( !pair.feature().isValid() )
×
1942
  {
1943
    return;
×
1944
  }
1945

1946
  if ( !pair.layer()->isEditable() )
×
1947
  {
1948
    pair.layer()->startEditing();
×
1949
  }
×
1950

1951
  QgsFeature f( pair.feature() );
×
1952
  pair.layer()->updateFeature( f );
×
1953
  pair.layer()->commitChanges();
×
1954
  pair.layer()->triggerRepaint();
×
1955
}
×
1956

1957
QString InputUtils::imageGalleryLocation()
×
1958
{
1959
  QStringList galleryPaths = QStandardPaths::standardLocations( QStandardPaths::PicturesLocation );
×
1960

1961
  if ( galleryPaths.isEmpty() )
×
1962
  {
1963
    CoreUtils::log( QStringLiteral( "Image Picker" ), QStringLiteral( "Could not find standard path to image gallery" ) );
×
1964
    return QString();
×
1965
  }
1966

1967
  return galleryPaths.last();
×
1968
}
×
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