1 /***************************************************************************
2   qgsquickutils.cpp
3   --------------------------------------
4   Date                 : Nov 2017
5   Copyright            : (C) 2017 by Peter Petrik
6   Email                : zilolv at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include <QGuiApplication>
17 #include <QScreen>
18 #include <QString>
19 #include <QWindow>
20 
21 #include "qgis.h"
22 #include "qgscoordinatereferencesystem.h"
23 #include "qgscoordinatetransform.h"
24 #include "qgsdistancearea.h"
25 #include "qgslogger.h"
26 #include "qgsvectorlayer.h"
27 #include "qgsfeature.h"
28 #include "qgsapplication.h"
29 #include "qgsvaluerelationfieldformatter.h"
30 #include "qgsdatetimefieldformatter.h"
31 
32 #include "qgsquickfeaturelayerpair.h"
33 #include "qgsquickmapsettings.h"
34 #include "qgsquickutils.h"
35 #include "qgsunittypes.h"
36 
37 
QgsQuickUtils(QObject * parent)38 QgsQuickUtils::QgsQuickUtils( QObject *parent )
39   : QObject( parent )
40   , mScreenDensity( calculateScreenDensity() )
41 {
42 }
43 
44 /**
45  * Makes QgsCoordinateReferenceSystem::fromEpsgId accessible for QML components
46  */
coordinateReferenceSystemFromEpsgId(long epsg)47 QgsCoordinateReferenceSystem QgsQuickUtils::coordinateReferenceSystemFromEpsgId( long epsg )
48 {
49   return QgsCoordinateReferenceSystem::fromEpsgId( epsg );
50 }
51 
pointXY(double x,double y)52 QgsPointXY QgsQuickUtils::pointXY( double x, double y )
53 {
54   return QgsPointXY( x, y );
55 }
56 
point(double x,double y,double z,double m)57 QgsPoint QgsQuickUtils::point( double x, double y, double z, double m )
58 {
59   return QgsPoint( x, y, z, m );
60 }
61 
coordinateToPoint(const QGeoCoordinate & coor)62 QgsPoint QgsQuickUtils::coordinateToPoint( const QGeoCoordinate &coor )
63 {
64   return QgsPoint( coor.longitude(), coor.latitude(), coor.altitude() );
65 }
66 
transformPoint(const QgsCoordinateReferenceSystem & srcCrs,const QgsCoordinateReferenceSystem & destCrs,const QgsCoordinateTransformContext & context,const QgsPointXY & srcPoint)67 QgsPointXY QgsQuickUtils::transformPoint( const QgsCoordinateReferenceSystem &srcCrs,
68     const QgsCoordinateReferenceSystem &destCrs,
69     const QgsCoordinateTransformContext &context,
70     const QgsPointXY &srcPoint )
71 {
72   QgsCoordinateTransform mTransform( srcCrs, destCrs, context );
73   QgsPointXY pt = mTransform.transform( srcPoint );
74   return pt;
75 }
76 
screenUnitsToMeters(QgsQuickMapSettings * mapSettings,int baseLengthPixels)77 double QgsQuickUtils::screenUnitsToMeters( QgsQuickMapSettings *mapSettings, int baseLengthPixels )
78 {
79   if ( mapSettings == nullptr ) return 0.0;
80 
81   QgsDistanceArea mDistanceArea;
82   mDistanceArea.setEllipsoid( QStringLiteral( "WGS84" ) );
83   mDistanceArea.setSourceCrs( mapSettings->destinationCrs(), mapSettings->transformContext() );
84 
85   // calculate the geographic distance from the central point of extent
86   // to the specified number of points on the right side
87   QSize s = mapSettings->outputSize();
88   QPoint pointCenter( s.width() / 2, s.height() / 2 );
89   QgsPointXY p1 = mapSettings->screenToCoordinate( pointCenter );
90   QgsPointXY p2 = mapSettings->screenToCoordinate( pointCenter + QPoint( baseLengthPixels, 0 ) );
91   return mDistanceArea.measureLine( p1, p2 );
92 }
93 
fileExists(const QString & path)94 bool QgsQuickUtils::fileExists( const QString &path )
95 {
96   QFileInfo check_file( path );
97   // check if file exists and if yes: Is it really a file and no directory?
98   return ( check_file.exists() && check_file.isFile() );
99 }
100 
getRelativePath(const QString & path,const QString & prefixPath)101 QString QgsQuickUtils::getRelativePath( const QString &path, const QString &prefixPath )
102 {
103   QString modPath = path;
104   QString filePrefix( "file://" );
105 
106   if ( path.startsWith( filePrefix ) )
107   {
108     modPath = modPath.replace( filePrefix, QString() );
109   }
110 
111   if ( prefixPath.isEmpty() ) return modPath;
112 
113   // Do not use a canonical path for non-existing path
114   if ( !QFileInfo( path ).exists() )
115   {
116     if ( !prefixPath.isEmpty() && modPath.startsWith( prefixPath ) )
117     {
118       return modPath.replace( prefixPath, QString() );
119     }
120   }
121   else
122   {
123     QDir absoluteDir( modPath );
124     QDir prefixDir( prefixPath );
125     QString canonicalPath = absoluteDir.canonicalPath();
126     QString prefixCanonicalPath = prefixDir.canonicalPath() + "/";
127 
128     if ( prefixCanonicalPath.length() > 1 && canonicalPath.startsWith( prefixCanonicalPath ) )
129     {
130       return canonicalPath.replace( prefixCanonicalPath, QString() );
131     }
132   }
133 
134   return QString();
135 }
136 
logMessage(const QString & message,const QString & tag,Qgis::MessageLevel level)137 void QgsQuickUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
138 {
139   QgsMessageLog::logMessage( message, tag, level );
140 }
141 
featureFactory(const QgsFeature & feature,QgsVectorLayer * layer)142 QgsQuickFeatureLayerPair QgsQuickUtils::featureFactory( const QgsFeature &feature, QgsVectorLayer *layer )
143 {
144   return QgsQuickFeatureLayerPair( feature, layer );
145 }
146 
getThemeIcon(const QString & name)147 const QUrl QgsQuickUtils::getThemeIcon( const QString &name )
148 {
149   QString path = QStringLiteral( "qrc:/%1.svg" ).arg( name );
150   QgsDebugMsg( QStringLiteral( "Using icon %1 from %2" ).arg( name, path ) );
151   return QUrl( path );
152 }
153 
getEditorComponentSource(const QString & widgetName)154 const QUrl QgsQuickUtils::getEditorComponentSource( const QString &widgetName )
155 {
156   QString path( "qgsquick%1.qml" );
157   QStringList supportedWidgets = { QStringLiteral( "textedit" ),
158                                    QStringLiteral( "valuemap" ),
159                                    QStringLiteral( "valuerelation" ),
160                                    QStringLiteral( "checkbox" ),
161                                    QStringLiteral( "externalresource" ),
162                                    QStringLiteral( "datetime" ),
163                                    QStringLiteral( "range" )
164                                  };
165   if ( supportedWidgets.contains( widgetName ) )
166   {
167     return QUrl( path.arg( widgetName ) );
168   }
169   else
170   {
171     return QUrl( path.arg( QLatin1String( "textedit" ) ) );
172   }
173 }
174 
formatPoint(const QgsPoint & point,QgsCoordinateFormatter::Format format,int decimals,QgsCoordinateFormatter::FormatFlags flags)175 QString QgsQuickUtils::formatPoint(
176   const QgsPoint &point,
177   QgsCoordinateFormatter::Format format,
178   int decimals,
179   QgsCoordinateFormatter::FormatFlags flags )
180 {
181   return QgsCoordinateFormatter::format( point, format, decimals, flags );
182 }
183 
formatDistance(double distance,QgsUnitTypes::DistanceUnit units,int decimals,QgsUnitTypes::SystemOfMeasurement destSystem)184 QString QgsQuickUtils::formatDistance( double distance,
185                                        QgsUnitTypes::DistanceUnit units,
186                                        int decimals,
187                                        QgsUnitTypes::SystemOfMeasurement destSystem )
188 {
189   double destDistance;
190   QgsUnitTypes::DistanceUnit destUnits;
191 
192   humanReadableDistance( distance, units, destSystem, destDistance, destUnits );
193 
194   return QStringLiteral( "%1 %2" )
195          .arg( QString::number( destDistance, 'f', decimals ) )
196          .arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
197 }
198 
removeFile(const QString & filePath)199 bool QgsQuickUtils::removeFile( const QString &filePath )
200 {
201   QFile file( filePath );
202   return file.remove( filePath );
203 }
204 
205 
humanReadableDistance(double srcDistance,QgsUnitTypes::DistanceUnit srcUnits,QgsUnitTypes::SystemOfMeasurement destSystem,double & destDistance,QgsUnitTypes::DistanceUnit & destUnits)206 void QgsQuickUtils::humanReadableDistance( double srcDistance, QgsUnitTypes::DistanceUnit srcUnits,
207     QgsUnitTypes::SystemOfMeasurement destSystem,
208     double &destDistance, QgsUnitTypes::DistanceUnit &destUnits )
209 {
210   if ( ( destSystem == QgsUnitTypes::MetricSystem ) || ( destSystem == QgsUnitTypes::UnknownSystem ) )
211   {
212     return formatToMetricDistance( srcDistance, srcUnits, destDistance, destUnits );
213   }
214   else if ( destSystem == QgsUnitTypes::ImperialSystem )
215   {
216     return formatToImperialDistance( srcDistance, srcUnits, destDistance, destUnits );
217   }
218   else if ( destSystem == QgsUnitTypes::USCSSystem )
219   {
220     return formatToUSCSDistance( srcDistance, srcUnits, destDistance, destUnits );
221   }
222   else
223   {
224     Q_ASSERT( false ); //should never happen
225   }
226 }
227 
formatToMetricDistance(double srcDistance,QgsUnitTypes::DistanceUnit srcUnits,double & destDistance,QgsUnitTypes::DistanceUnit & destUnits)228 void QgsQuickUtils::formatToMetricDistance( double srcDistance,
229     QgsUnitTypes::DistanceUnit srcUnits,
230     double &destDistance,
231     QgsUnitTypes::DistanceUnit &destUnits )
232 {
233   double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, QgsUnitTypes::DistanceMillimeters );
234   if ( dist < 0 )
235   {
236     destDistance = 0;
237     destUnits = QgsUnitTypes::DistanceMillimeters;
238     return;
239   }
240 
241   double mmToKm = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceKilometers, QgsUnitTypes::DistanceMillimeters );
242   if ( dist > mmToKm )
243   {
244     destDistance = dist / mmToKm;
245     destUnits = QgsUnitTypes::DistanceKilometers;
246     return;
247   }
248 
249   double mmToM = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceMeters, QgsUnitTypes::DistanceMillimeters );
250   if ( dist > mmToM )
251   {
252     destDistance = dist / mmToM;
253     destUnits = QgsUnitTypes::DistanceMeters;
254     return;
255   }
256 
257   double mmToCm = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceCentimeters, QgsUnitTypes::DistanceMillimeters );
258   if ( dist > mmToCm )
259   {
260     destDistance = dist / mmToCm;
261     destUnits = QgsUnitTypes::DistanceCentimeters;
262     return;
263   }
264 
265   destDistance = dist;
266   destUnits = QgsUnitTypes::DistanceMillimeters;
267 }
268 
formatToImperialDistance(double srcDistance,QgsUnitTypes::DistanceUnit srcUnits,double & destDistance,QgsUnitTypes::DistanceUnit & destUnits)269 void QgsQuickUtils::formatToImperialDistance( double srcDistance,
270     QgsUnitTypes::DistanceUnit srcUnits,
271     double &destDistance,
272     QgsUnitTypes::DistanceUnit &destUnits )
273 {
274   double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, QgsUnitTypes::DistanceFeet );
275   if ( dist < 0 )
276   {
277     destDistance = 0;
278     destUnits = QgsUnitTypes::DistanceFeet;
279     return;
280   }
281 
282   double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceMiles, QgsUnitTypes::DistanceFeet );
283   if ( dist > feetToMile )
284   {
285     destDistance = dist / feetToMile;
286     destUnits = QgsUnitTypes::DistanceMiles;
287     return;
288   }
289 
290   double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceYards, QgsUnitTypes::DistanceFeet );
291   if ( dist > feetToYard )
292   {
293     destDistance = dist / feetToYard;
294     destUnits = QgsUnitTypes::DistanceYards;
295     return;
296   }
297 
298   destDistance = dist;
299   destUnits = QgsUnitTypes::DistanceFeet;
300   return;
301 }
302 
formatToUSCSDistance(double srcDistance,QgsUnitTypes::DistanceUnit srcUnits,double & destDistance,QgsUnitTypes::DistanceUnit & destUnits)303 void QgsQuickUtils::formatToUSCSDistance( double srcDistance,
304     QgsUnitTypes::DistanceUnit srcUnits,
305     double &destDistance,
306     QgsUnitTypes::DistanceUnit &destUnits )
307 {
308   double dist = srcDistance * QgsUnitTypes::fromUnitToUnitFactor( srcUnits, QgsUnitTypes::DistanceFeet );
309   if ( dist < 0 )
310   {
311     destDistance = 0;
312     destUnits = QgsUnitTypes::DistanceFeet;
313     return;
314   }
315 
316   double feetToMile = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceNauticalMiles, QgsUnitTypes::DistanceFeet );
317   if ( dist > feetToMile )
318   {
319     destDistance = dist / feetToMile;
320     destUnits = QgsUnitTypes::DistanceNauticalMiles;
321     return;
322   }
323 
324   double feetToYard = QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::DistanceYards, QgsUnitTypes::DistanceFeet );
325   if ( dist > feetToYard )
326   {
327     destDistance = dist / feetToYard;
328     destUnits = QgsUnitTypes::DistanceYards;
329     return;
330   }
331 
332   destDistance = dist;
333   destUnits = QgsUnitTypes::DistanceFeet;
334   return;
335 }
336 
dumpScreenInfo() const337 QString QgsQuickUtils::dumpScreenInfo() const
338 {
339   // take the first top level window
340   QScreen *screen = QGuiApplication::topLevelWindows().at( 0 )->screen();
341   double dpiX = screen->physicalDotsPerInchX();
342   double dpiY = screen->physicalDotsPerInchY();
343   int height = screen->geometry().height();
344   int width = screen->geometry().width();
345   double sizeX = static_cast<double>( width ) / dpiX * 25.4;
346   double sizeY = static_cast<double>( height ) / dpiY * 25.4;
347 
348   QString msg;
349   msg += tr( "screen resolution: %1x%2 px\n" ).arg( width ).arg( height );
350   msg += tr( "screen DPI: %1x%2\n" ).arg( dpiX ).arg( dpiY );
351   msg += tr( "screen size: %1x%2 mm\n" ).arg( QString::number( sizeX, 'f', 0 ), QString::number( sizeY, 'f', 0 ) );
352   msg += tr( "screen density: %1" ).arg( mScreenDensity );
353   return msg;
354 }
355 
createValueRelationCache(const QVariantMap & config,const QgsFeature & formFeature)356 QVariantMap QgsQuickUtils::createValueRelationCache( const QVariantMap &config, const QgsFeature &formFeature )
357 {
358   QVariantMap valueMap;
359   QgsValueRelationFieldFormatter::ValueRelationCache cache = QgsValueRelationFieldFormatter::createCache( config, formFeature );
360 
361   for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : qgis::as_const( cache ) )
362   {
363     valueMap.insert( item.key.toString(), item.value );
364   }
365   return valueMap;
366 }
367 
evaluateExpression(const QgsQuickFeatureLayerPair & pair,QgsProject * activeProject,const QString & expression)368 QString QgsQuickUtils::evaluateExpression( const QgsQuickFeatureLayerPair &pair, QgsProject *activeProject, const QString &expression )
369 {
370   QList<QgsExpressionContextScope *> scopes;
371   scopes << QgsExpressionContextUtils::globalScope();
372   scopes << QgsExpressionContextUtils::projectScope( activeProject );
373   scopes << QgsExpressionContextUtils::layerScope( pair.layer() );
374 
375   QgsExpressionContext context( scopes );
376   context.setFeature( pair.feature() );
377   QgsExpression expr( expression );
378   return expr.evaluate( &context ).toString();
379 }
380 
selectFeaturesInLayer(QgsVectorLayer * layer,const QList<int> & fids,QgsVectorLayer::SelectBehavior behavior)381 void QgsQuickUtils::selectFeaturesInLayer( QgsVectorLayer *layer, const QList<int> &fids, QgsVectorLayer::SelectBehavior behavior )
382 {
383   QgsFeatureIds qgsFids;
384   for ( const int &fid : fids )
385     qgsFids << fid;
386   layer->selectByIds( qgsFids, behavior );
387 }
388 
fieldType(const QgsField & field)389 QString QgsQuickUtils::fieldType( const QgsField &field )
390 {
391   return QVariant( field.type() ).typeName();
392 }
393 
dateTimeFieldFormat(const QString & fieldFormat)394 QString QgsQuickUtils::dateTimeFieldFormat( const QString &fieldFormat )
395 {
396   if ( QgsDateTimeFieldFormatter::DATE_FORMAT == fieldFormat )
397   {
398     return QString( "Date" );
399   }
400   else if ( QgsDateTimeFieldFormatter::TIME_FORMAT == fieldFormat )
401   {
402     return QString( "Time" );
403   }
404   else if ( QgsDateTimeFieldFormatter::DATETIME_FORMAT == fieldFormat )
405   {
406     return QString( "Date Time" );
407   }
408   else
409   {
410     return QString( "Date Time" );
411   }
412 }
413 
screenDensity() const414 qreal QgsQuickUtils::screenDensity() const
415 {
416   return mScreenDensity;
417 }
calculateScreenDensity()418 qreal QgsQuickUtils::calculateScreenDensity()
419 {
420   // calculate screen density for calculation of real pixel sizes from density-independent pixels
421   // take the first top level window
422   QScreen *screen = QGuiApplication::topLevelWindows().at( 0 )->screen();
423   double dpiX = screen->physicalDotsPerInchX();
424   double dpiY = screen->physicalDotsPerInchY();
425   double dpi = dpiX < dpiY ? dpiX : dpiY; // In case of asymmetrical DPI. Improbable
426   return dpi / 160.;  // 160 DPI is baseline for density-independent pixels in Android
427 }
428