LCOV - code coverage report
Current view: top level - core - qgsogrutils.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 110 638 17.2 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                              qgsogrutils.cpp
       3                 :            :                              ---------------
       4                 :            :     begin                : February 2016
       5                 :            :     copyright            : (C) 2016 Nyall Dawson
       6                 :            :     email                : nyall dot dawson 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 "qgsogrutils.h"
      17                 :            : #include "qgsapplication.h"
      18                 :            : #include "qgslogger.h"
      19                 :            : #include "qgsgeometry.h"
      20                 :            : #include "qgsfields.h"
      21                 :            : #include "qgslinestring.h"
      22                 :            : #include "qgsmultipoint.h"
      23                 :            : #include "qgsmultilinestring.h"
      24                 :            : #include "qgsogrprovider.h"
      25                 :            : #include "qgslinesymbollayer.h"
      26                 :            : #include "qgspolygon.h"
      27                 :            : #include "qgsmultipolygon.h"
      28                 :            : 
      29                 :            : #include <QTextCodec>
      30                 :            : #include <QUuid>
      31                 :            : #include <cpl_error.h>
      32                 :            : #include <QJsonDocument>
      33                 :            : #include <QFileInfo>
      34                 :            : #include <QDir>
      35                 :            : #include <QTextStream>
      36                 :            : #include <QDataStream>
      37                 :            : #include <QRegularExpression>
      38                 :            : 
      39                 :            : #include "ogr_srs_api.h"
      40                 :            : 
      41                 :            : // Starting with GDAL 2.2, there are 2 concepts: unset fields and null fields
      42                 :            : // whereas previously there was only unset fields. For QGIS purposes, both
      43                 :            : // states (unset/null) are equivalent.
      44                 :            : #ifndef OGRNullMarker
      45                 :            : #define OGR_F_IsFieldSetAndNotNull OGR_F_IsFieldSet
      46                 :            : #endif
      47                 :            : 
      48                 :            : 
      49                 :            : 
      50                 :          0 : void gdal::OGRDataSourceDeleter::operator()( OGRDataSourceH source )
      51                 :            : {
      52                 :          0 :   OGR_DS_Destroy( source );
      53                 :          0 : }
      54                 :            : 
      55                 :            : 
      56                 :          0 : void gdal::OGRGeometryDeleter::operator()( OGRGeometryH geometry )
      57                 :            : {
      58                 :          0 :   OGR_G_DestroyGeometry( geometry );
      59                 :          0 : }
      60                 :            : 
      61                 :          0 : void gdal::OGRFldDeleter::operator()( OGRFieldDefnH definition )
      62                 :            : {
      63                 :          0 :   OGR_Fld_Destroy( definition );
      64                 :          0 : }
      65                 :            : 
      66                 :       1134 : void gdal::OGRFeatureDeleter::operator()( OGRFeatureH feature )
      67                 :            : {
      68                 :       1134 :   OGR_F_Destroy( feature );
      69                 :       1134 : }
      70                 :            : 
      71                 :          0 : void gdal::GDALDatasetCloser::operator()( GDALDatasetH dataset )
      72                 :            : {
      73                 :          0 :   GDALClose( dataset );
      74                 :          0 : }
      75                 :            : 
      76                 :          0 : void gdal::fast_delete_and_close( gdal::dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path )
      77                 :            : {
      78                 :            :   // see https://github.com/qgis/QGIS/commit/d024910490a39e65e671f2055c5b6543e06c7042#commitcomment-25194282
      79                 :            :   // faster if we close the handle AFTER delete, but doesn't work for windows
      80                 :            : #ifdef Q_OS_WIN
      81                 :            :   // close dataset handle
      82                 :            :   dataset.reset();
      83                 :            : #endif
      84                 :            : 
      85                 :          0 :   CPLPushErrorHandler( CPLQuietErrorHandler );
      86                 :          0 :   GDALDeleteDataset( driver, path.toUtf8().constData() );
      87                 :          0 :   CPLPopErrorHandler();
      88                 :            : 
      89                 :            : #ifndef Q_OS_WIN
      90                 :            :   // close dataset handle
      91                 :          0 :   dataset.reset();
      92                 :            : #endif
      93                 :          0 : }
      94                 :            : 
      95                 :            : 
      96                 :          0 : void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options )
      97                 :            : {
      98                 :          0 :   GDALDestroyWarpOptions( options );
      99                 :          0 : }
     100                 :            : 
     101                 :          0 : QgsFeature QgsOgrUtils::readOgrFeature( OGRFeatureH ogrFet, const QgsFields &fields, QTextCodec *encoding )
     102                 :            : {
     103                 :          0 :   QgsFeature feature;
     104                 :          0 :   if ( !ogrFet )
     105                 :            :   {
     106                 :          0 :     feature.setValid( false );
     107                 :          0 :     return feature;
     108                 :            :   }
     109                 :            : 
     110                 :          0 :   feature.setId( OGR_F_GetFID( ogrFet ) );
     111                 :          0 :   feature.setValid( true );
     112                 :            : 
     113                 :          0 :   if ( !readOgrFeatureGeometry( ogrFet, feature ) )
     114                 :            :   {
     115                 :          0 :     feature.setValid( false );
     116                 :          0 :   }
     117                 :            : 
     118                 :          0 :   if ( !readOgrFeatureAttributes( ogrFet, fields, feature, encoding ) )
     119                 :            :   {
     120                 :          0 :     feature.setValid( false );
     121                 :          0 :   }
     122                 :            : 
     123                 :          0 :   return feature;
     124                 :          0 : }
     125                 :            : 
     126                 :          0 : QgsFields QgsOgrUtils::readOgrFields( OGRFeatureH ogrFet, QTextCodec *encoding )
     127                 :            : {
     128                 :          0 :   QgsFields fields;
     129                 :            : 
     130                 :          0 :   if ( !ogrFet )
     131                 :          0 :     return fields;
     132                 :            : 
     133                 :          0 :   int fieldCount = OGR_F_GetFieldCount( ogrFet );
     134                 :          0 :   for ( int i = 0; i < fieldCount; ++i )
     135                 :            :   {
     136                 :          0 :     OGRFieldDefnH fldDef = OGR_F_GetFieldDefnRef( ogrFet, i );
     137                 :          0 :     if ( !fldDef )
     138                 :            :     {
     139                 :          0 :       fields.append( QgsField() );
     140                 :          0 :       continue;
     141                 :            :     }
     142                 :            : 
     143                 :          0 :     QString name = encoding ? encoding->toUnicode( OGR_Fld_GetNameRef( fldDef ) ) : QString::fromUtf8( OGR_Fld_GetNameRef( fldDef ) );
     144                 :            :     QVariant::Type varType;
     145                 :          0 :     switch ( OGR_Fld_GetType( fldDef ) )
     146                 :            :     {
     147                 :            :       case OFTInteger:
     148                 :          0 :         if ( OGR_Fld_GetSubType( fldDef ) == OFSTBoolean )
     149                 :          0 :           varType = QVariant::Bool;
     150                 :            :         else
     151                 :          0 :           varType = QVariant::Int;
     152                 :          0 :         break;
     153                 :            :       case OFTInteger64:
     154                 :          0 :         varType = QVariant::LongLong;
     155                 :          0 :         break;
     156                 :            :       case OFTReal:
     157                 :          0 :         varType = QVariant::Double;
     158                 :          0 :         break;
     159                 :            :       case OFTDate:
     160                 :          0 :         varType = QVariant::Date;
     161                 :          0 :         break;
     162                 :            :       case OFTTime:
     163                 :          0 :         varType = QVariant::Time;
     164                 :          0 :         break;
     165                 :            :       case OFTDateTime:
     166                 :          0 :         varType = QVariant::DateTime;
     167                 :          0 :         break;
     168                 :            :       case OFTString:
     169                 :          0 :         if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON )
     170                 :          0 :           varType = QVariant::Map;
     171                 :            :         else
     172                 :          0 :           varType = QVariant::String;
     173                 :          0 :         break;
     174                 :            :       default:
     175                 :          0 :         varType = QVariant::String; // other unsupported, leave it as a string
     176                 :          0 :     }
     177                 :          0 :     fields.append( QgsField( name, varType ) );
     178                 :          0 :   }
     179                 :          0 :   return fields;
     180                 :          0 : }
     181                 :            : 
     182                 :            : 
     183                 :       1564 : QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsFields &fields, int attIndex, QTextCodec *encoding, bool *ok )
     184                 :            : {
     185                 :       1564 :   if ( attIndex < 0 || attIndex >= fields.count() )
     186                 :            :   {
     187                 :          0 :     if ( ok )
     188                 :          0 :       *ok = false;
     189                 :          0 :     return QVariant();
     190                 :            :   }
     191                 :            : 
     192                 :       1564 :   const QgsField field = fields.at( attIndex );
     193                 :       1564 :   return getOgrFeatureAttribute( ogrFet, field, attIndex, encoding, ok );
     194                 :       1564 : }
     195                 :            : 
     196                 :       1564 : QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField &field, int attIndex, QTextCodec *encoding, bool *ok )
     197                 :            : {
     198                 :       1564 :   if ( !ogrFet || attIndex < 0 )
     199                 :            :   {
     200                 :          0 :     if ( ok )
     201                 :          0 :       *ok = false;
     202                 :          0 :     return QVariant();
     203                 :            :   }
     204                 :            : 
     205                 :       1564 :   OGRFieldDefnH fldDef = OGR_F_GetFieldDefnRef( ogrFet, attIndex );
     206                 :            : 
     207                 :       1564 :   if ( ! fldDef )
     208                 :            :   {
     209                 :          0 :     if ( ok )
     210                 :          0 :       *ok = false;
     211                 :            : 
     212                 :          0 :     QgsDebugMsg( QStringLiteral( "ogrFet->GetFieldDefnRef(attindex) returns NULL" ) );
     213                 :          0 :     return QVariant();
     214                 :            :   }
     215                 :            : 
     216                 :       1564 :   QVariant value;
     217                 :            : 
     218                 :       1564 :   if ( ok )
     219                 :       1564 :     *ok = true;
     220                 :            : 
     221                 :       1564 :   if ( OGR_F_IsFieldSetAndNotNull( ogrFet, attIndex ) )
     222                 :            :   {
     223                 :       1048 :     switch ( field.type() )
     224                 :            :     {
     225                 :            :       case QVariant::String:
     226                 :            :       {
     227                 :          1 :         if ( encoding )
     228                 :          1 :           value = QVariant( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
     229                 :            :         else
     230                 :          0 :           value = QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
     231                 :            : 
     232                 :            : #ifdef Q_OS_WIN
     233                 :            :         // Fixes GH #41076 (empty strings shown as NULL), because we have checked before that it was NOT NULL
     234                 :            :         // Note:  QVariant( QString( ) ).isNull( ) is still true on windows so we really need string literal :(
     235                 :            :         if ( value.isNull() )
     236                 :            :           value = QVariant( QStringLiteral( "" ) ); // skip-keyword-check
     237                 :            : #endif
     238                 :            : 
     239                 :          1 :         break;
     240                 :            :       }
     241                 :            :       case QVariant::Int:
     242                 :        231 :         value = QVariant( OGR_F_GetFieldAsInteger( ogrFet, attIndex ) );
     243                 :        231 :         break;
     244                 :            :       case QVariant::Bool:
     245                 :          0 :         value = QVariant( bool( OGR_F_GetFieldAsInteger( ogrFet, attIndex ) ) );
     246                 :          0 :         break;
     247                 :            :       case QVariant::LongLong:
     248                 :        816 :         value = QVariant( OGR_F_GetFieldAsInteger64( ogrFet, attIndex ) );
     249                 :        816 :         break;
     250                 :            :       case QVariant::Double:
     251                 :          0 :         value = QVariant( OGR_F_GetFieldAsDouble( ogrFet, attIndex ) );
     252                 :          0 :         break;
     253                 :            :       case QVariant::Date:
     254                 :            :       case QVariant::DateTime:
     255                 :            :       case QVariant::Time:
     256                 :            :       {
     257                 :            :         int year, month, day, hour, minute, second, tzf;
     258                 :            : 
     259                 :          0 :         OGR_F_GetFieldAsDateTime( ogrFet, attIndex, &year, &month, &day, &hour, &minute, &second, &tzf );
     260                 :          0 :         if ( field.type() == QVariant::Date )
     261                 :          0 :           value = QDate( year, month, day );
     262                 :          0 :         else if ( field.type() == QVariant::Time )
     263                 :          0 :           value = QTime( hour, minute, second );
     264                 :            :         else
     265                 :          0 :           value = QDateTime( QDate( year, month, day ), QTime( hour, minute, second ) );
     266                 :            :       }
     267                 :          0 :       break;
     268                 :            : 
     269                 :            :       case QVariant::ByteArray:
     270                 :            :       {
     271                 :          0 :         int size = 0;
     272                 :          0 :         const GByte *b = OGR_F_GetFieldAsBinary( ogrFet, attIndex, &size );
     273                 :            : 
     274                 :            :         // QByteArray::fromRawData is funny. It doesn't take ownership of the data, so we have to explicitly call
     275                 :            :         // detach on it to force a copy which owns the data
     276                 :          0 :         QByteArray ba = QByteArray::fromRawData( reinterpret_cast<const char *>( b ), size );
     277                 :          0 :         ba.detach();
     278                 :            : 
     279                 :          0 :         value = ba;
     280                 :            :         break;
     281                 :          0 :       }
     282                 :            : 
     283                 :            :       case QVariant::StringList:
     284                 :            :       {
     285                 :          0 :         QStringList list;
     286                 :          0 :         char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
     287                 :          0 :         const int count = CSLCount( lst );
     288                 :          0 :         if ( count > 0 )
     289                 :            :         {
     290                 :          0 :           list.reserve( count );
     291                 :          0 :           for ( int i = 0; i < count; i++ )
     292                 :            :           {
     293                 :          0 :             if ( encoding )
     294                 :          0 :               list << encoding->toUnicode( lst[i] );
     295                 :            :             else
     296                 :          0 :               list << QString::fromUtf8( lst[i] );
     297                 :          0 :           }
     298                 :          0 :         }
     299                 :          0 :         value = list;
     300                 :            :         break;
     301                 :          0 :       }
     302                 :            : 
     303                 :            :       case QVariant::List:
     304                 :            :       {
     305                 :          0 :         switch ( field.subType() )
     306                 :            :         {
     307                 :            :           case QVariant::String:
     308                 :            :           {
     309                 :          0 :             QStringList list;
     310                 :          0 :             char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
     311                 :          0 :             const int count = CSLCount( lst );
     312                 :          0 :             if ( count > 0 )
     313                 :            :             {
     314                 :          0 :               list.reserve( count );
     315                 :          0 :               for ( int i = 0; i < count; i++ )
     316                 :            :               {
     317                 :          0 :                 if ( encoding )
     318                 :          0 :                   list << encoding->toUnicode( lst[i] );
     319                 :            :                 else
     320                 :          0 :                   list << QString::fromUtf8( lst[i] );
     321                 :          0 :               }
     322                 :          0 :             }
     323                 :          0 :             value = list;
     324                 :            :             break;
     325                 :          0 :           }
     326                 :            : 
     327                 :            :           case QVariant::Int:
     328                 :            :           {
     329                 :          0 :             QVariantList list;
     330                 :          0 :             int count = 0;
     331                 :          0 :             const int *lst = OGR_F_GetFieldAsIntegerList( ogrFet, attIndex, &count );
     332                 :          0 :             if ( count > 0 )
     333                 :            :             {
     334                 :          0 :               list.reserve( count );
     335                 :          0 :               for ( int i = 0; i < count; i++ )
     336                 :            :               {
     337                 :          0 :                 list << lst[i];
     338                 :          0 :               }
     339                 :          0 :             }
     340                 :          0 :             value = list;
     341                 :            :             break;
     342                 :          0 :           }
     343                 :            : 
     344                 :            :           case QVariant::Double:
     345                 :            :           {
     346                 :          0 :             QVariantList list;
     347                 :          0 :             int count = 0;
     348                 :          0 :             const double *lst = OGR_F_GetFieldAsDoubleList( ogrFet, attIndex, &count );
     349                 :          0 :             if ( count > 0 )
     350                 :            :             {
     351                 :          0 :               list.reserve( count );
     352                 :          0 :               for ( int i = 0; i < count; i++ )
     353                 :            :               {
     354                 :          0 :                 list << lst[i];
     355                 :          0 :               }
     356                 :          0 :             }
     357                 :          0 :             value = list;
     358                 :            :             break;
     359                 :          0 :           }
     360                 :            : 
     361                 :            :           case QVariant::LongLong:
     362                 :            :           {
     363                 :          0 :             QVariantList list;
     364                 :          0 :             int count = 0;
     365                 :          0 :             const long long *lst = OGR_F_GetFieldAsInteger64List( ogrFet, attIndex, &count );
     366                 :          0 :             if ( count > 0 )
     367                 :            :             {
     368                 :          0 :               list.reserve( count );
     369                 :          0 :               for ( int i = 0; i < count; i++ )
     370                 :            :               {
     371                 :          0 :                 list << lst[i];
     372                 :          0 :               }
     373                 :          0 :             }
     374                 :          0 :             value = list;
     375                 :            :             break;
     376                 :          0 :           }
     377                 :            : 
     378                 :            :           default:
     379                 :            :           {
     380                 :            :             Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
     381                 :          0 :             if ( ok )
     382                 :          0 :               *ok = false;
     383                 :          0 :             break;
     384                 :            :           }
     385                 :            :         }
     386                 :          0 :         break;
     387                 :            :       }
     388                 :            : 
     389                 :            :       case QVariant::Map:
     390                 :            :       {
     391                 :            :         //it has to be JSON
     392                 :            :         //it's null if no json format
     393                 :          0 :         if ( encoding )
     394                 :          0 :           value = QJsonDocument::fromJson( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant();
     395                 :            :         else
     396                 :          0 :           value = QJsonDocument::fromJson( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant();
     397                 :          0 :         break;
     398                 :            :       }
     399                 :            :       default:
     400                 :            :         Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
     401                 :          0 :         if ( ok )
     402                 :          0 :           *ok = false;
     403                 :          0 :     }
     404                 :       1048 :   }
     405                 :            :   else
     406                 :            :   {
     407                 :        516 :     value = QVariant( field.type() );
     408                 :            :   }
     409                 :            : 
     410                 :       1564 :   return value;
     411                 :       1564 : }
     412                 :            : 
     413                 :          0 : bool QgsOgrUtils::readOgrFeatureAttributes( OGRFeatureH ogrFet, const QgsFields &fields, QgsFeature &feature, QTextCodec *encoding )
     414                 :            : {
     415                 :            :   // read all attributes
     416                 :          0 :   feature.initAttributes( fields.count() );
     417                 :          0 :   feature.setFields( fields );
     418                 :            : 
     419                 :          0 :   if ( !ogrFet )
     420                 :          0 :     return false;
     421                 :            : 
     422                 :          0 :   bool ok = false;
     423                 :          0 :   for ( int idx = 0; idx < fields.count(); ++idx )
     424                 :            :   {
     425                 :          0 :     QVariant value = getOgrFeatureAttribute( ogrFet, fields, idx, encoding, &ok );
     426                 :          0 :     if ( ok )
     427                 :            :     {
     428                 :          0 :       feature.setAttribute( idx, value );
     429                 :          0 :     }
     430                 :          0 :   }
     431                 :          0 :   return true;
     432                 :          0 : }
     433                 :            : 
     434                 :          0 : bool QgsOgrUtils::readOgrFeatureGeometry( OGRFeatureH ogrFet, QgsFeature &feature )
     435                 :            : {
     436                 :          0 :   if ( !ogrFet )
     437                 :          0 :     return false;
     438                 :            : 
     439                 :          0 :   OGRGeometryH geom = OGR_F_GetGeometryRef( ogrFet );
     440                 :          0 :   if ( !geom )
     441                 :          0 :     feature.clearGeometry();
     442                 :            :   else
     443                 :          0 :     feature.setGeometry( ogrGeometryToQgsGeometry( geom ) );
     444                 :            : 
     445                 :          0 :   return true;
     446                 :          0 : }
     447                 :            : 
     448                 :        133 : std::unique_ptr< QgsPoint > ogrGeometryToQgsPoint( OGRGeometryH geom )
     449                 :            : {
     450                 :        133 :   QgsWkbTypes::Type wkbType = static_cast<QgsWkbTypes::Type>( OGR_G_GetGeometryType( geom ) );
     451                 :            : 
     452                 :            :   double x, y, z, m;
     453                 :        133 :   OGR_G_GetPointZM( geom, 0, &x, &y, &z, &m );
     454                 :        133 :   return std::make_unique< QgsPoint >( wkbType, x, y, z, m );
     455                 :            : }
     456                 :            : 
     457                 :          0 : std::unique_ptr< QgsMultiPoint > ogrGeometryToQgsMultiPoint( OGRGeometryH geom )
     458                 :            : {
     459                 :          0 :   std::unique_ptr< QgsMultiPoint > mp = std::make_unique< QgsMultiPoint >();
     460                 :            : 
     461                 :          0 :   const int count = OGR_G_GetGeometryCount( geom );
     462                 :          0 :   mp->reserve( count );
     463                 :          0 :   for ( int i = 0; i < count; ++i )
     464                 :            :   {
     465                 :          0 :     mp->addGeometry( ogrGeometryToQgsPoint( OGR_G_GetGeometryRef( geom, i ) ).release() );
     466                 :          0 :   }
     467                 :            : 
     468                 :          0 :   return mp;
     469                 :          0 : }
     470                 :            : 
     471                 :       1036 : std::unique_ptr< QgsLineString > ogrGeometryToQgsLineString( OGRGeometryH geom )
     472                 :            : {
     473                 :       1036 :   QgsWkbTypes::Type wkbType = static_cast<QgsWkbTypes::Type>( OGR_G_GetGeometryType( geom ) );
     474                 :            : 
     475                 :       1036 :   int count = OGR_G_GetPointCount( geom );
     476                 :       1036 :   QVector< double > x( count );
     477                 :       1036 :   QVector< double > y( count );
     478                 :       1036 :   QVector< double > z;
     479                 :       1036 :   double *pz = nullptr;
     480                 :       1036 :   if ( QgsWkbTypes::hasZ( wkbType ) )
     481                 :            :   {
     482                 :          0 :     z.resize( count );
     483                 :          0 :     pz = z.data();
     484                 :          0 :   }
     485                 :       1036 :   double *pm = nullptr;
     486                 :       1036 :   QVector< double > m;
     487                 :       1036 :   if ( QgsWkbTypes::hasM( wkbType ) )
     488                 :            :   {
     489                 :          0 :     m.resize( count );
     490                 :          0 :     pm = m.data();
     491                 :          0 :   }
     492                 :       1036 :   OGR_G_GetPointsZM( geom, x.data(), sizeof( double ), y.data(), sizeof( double ), pz, sizeof( double ), pm, sizeof( double ) );
     493                 :            : 
     494                 :       1036 :   return std::make_unique< QgsLineString>( x, y, z, m, wkbType == QgsWkbTypes::LineString25D );
     495                 :       1036 : }
     496                 :            : 
     497                 :         21 : std::unique_ptr< QgsMultiLineString > ogrGeometryToQgsMultiLineString( OGRGeometryH geom )
     498                 :            : {
     499                 :         21 :   std::unique_ptr< QgsMultiLineString > mp = std::make_unique< QgsMultiLineString >();
     500                 :            : 
     501                 :         21 :   const int count = OGR_G_GetGeometryCount( geom );
     502                 :         21 :   mp->reserve( count );
     503                 :         63 :   for ( int i = 0; i < count; ++i )
     504                 :            :   {
     505                 :         42 :     mp->addGeometry( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, i ) ).release() );
     506                 :         42 :   }
     507                 :            : 
     508                 :         21 :   return mp;
     509                 :         21 : }
     510                 :            : 
     511                 :        814 : std::unique_ptr< QgsPolygon > ogrGeometryToQgsPolygon( OGRGeometryH geom )
     512                 :            : {
     513                 :        814 :   std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
     514                 :            : 
     515                 :        814 :   const int count = OGR_G_GetGeometryCount( geom );
     516                 :        814 :   if ( count >= 1 )
     517                 :            :   {
     518                 :        814 :     polygon->setExteriorRing( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, 0 ) ).release() );
     519                 :        814 :   }
     520                 :            : 
     521                 :        841 :   for ( int i = 1; i < count; ++i )
     522                 :            :   {
     523                 :         27 :     polygon->addInteriorRing( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, i ) ).release() );
     524                 :         27 :   }
     525                 :            : 
     526                 :        814 :   return polygon;
     527                 :        814 : }
     528                 :            : 
     529                 :         26 : std::unique_ptr< QgsMultiPolygon > ogrGeometryToQgsMultiPolygon( OGRGeometryH geom )
     530                 :            : {
     531                 :         26 :   std::unique_ptr< QgsMultiPolygon > polygon = std::make_unique< QgsMultiPolygon >();
     532                 :            : 
     533                 :         26 :   const int count = OGR_G_GetGeometryCount( geom );
     534                 :         26 :   polygon->reserve( count );
     535                 :         72 :   for ( int i = 0; i < count; ++i )
     536                 :            :   {
     537                 :         46 :     polygon->addGeometry( ogrGeometryToQgsPolygon( OGR_G_GetGeometryRef( geom, i ) ).release() );
     538                 :         46 :   }
     539                 :            : 
     540                 :         26 :   return polygon;
     541                 :         26 : }
     542                 :            : 
     543                 :       1298 : QgsWkbTypes::Type QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGRwkbGeometryType ogrGeomType )
     544                 :            : {
     545                 :       1298 :   switch ( ogrGeomType )
     546                 :            :   {
     547                 :          0 :     case wkbUnknown: return QgsWkbTypes::Type::Unknown;
     548                 :        190 :     case wkbPoint: return QgsWkbTypes::Type::Point;
     549                 :        210 :     case wkbLineString: return QgsWkbTypes::Type::LineString;
     550                 :        848 :     case wkbPolygon: return QgsWkbTypes::Type::Polygon;
     551                 :          0 :     case wkbMultiPoint: return QgsWkbTypes::Type::MultiPoint;
     552                 :         21 :     case wkbMultiLineString: return QgsWkbTypes::Type::MultiLineString;
     553                 :         29 :     case wkbMultiPolygon: return QgsWkbTypes::Type::MultiPolygon;
     554                 :          0 :     case wkbGeometryCollection: return QgsWkbTypes::Type::GeometryCollection;
     555                 :          0 :     case wkbCircularString: return QgsWkbTypes::Type::CircularString;
     556                 :          0 :     case wkbCompoundCurve: return QgsWkbTypes::Type::CompoundCurve;
     557                 :          0 :     case wkbCurvePolygon: return QgsWkbTypes::Type::CurvePolygon;
     558                 :          0 :     case wkbMultiCurve: return QgsWkbTypes::Type::MultiCurve;
     559                 :          0 :     case wkbMultiSurface: return QgsWkbTypes::Type::MultiSurface;
     560                 :          0 :     case wkbCurve: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     561                 :          0 :     case wkbSurface: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     562                 :          0 :     case wkbPolyhedralSurface: return QgsWkbTypes::Type::Unknown; // no actual matching
     563                 :          0 :     case wkbTIN: return QgsWkbTypes::Type::Unknown; // no actual matching
     564                 :          0 :     case wkbTriangle: return QgsWkbTypes::Type::Triangle;
     565                 :            : 
     566                 :          0 :     case wkbNone: return QgsWkbTypes::Type::NoGeometry;
     567                 :          0 :     case wkbLinearRing: return QgsWkbTypes::Type::LineString; // approximate match
     568                 :            : 
     569                 :          0 :     case wkbCircularStringZ: return QgsWkbTypes::Type::CircularStringZ;
     570                 :          0 :     case wkbCompoundCurveZ: return QgsWkbTypes::Type::CompoundCurveZ;
     571                 :          0 :     case wkbCurvePolygonZ: return QgsWkbTypes::Type::CurvePolygonZ;
     572                 :          0 :     case wkbMultiCurveZ: return QgsWkbTypes::Type::MultiCurveZ;
     573                 :          0 :     case wkbMultiSurfaceZ: return QgsWkbTypes::Type::MultiSurfaceZ;
     574                 :          0 :     case wkbCurveZ: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     575                 :          0 :     case wkbSurfaceZ: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     576                 :          0 :     case wkbPolyhedralSurfaceZ: return QgsWkbTypes::Type::Unknown; // no actual matching
     577                 :          0 :     case wkbTINZ: return QgsWkbTypes::Type::Unknown; // no actual matching
     578                 :          0 :     case wkbTriangleZ: return QgsWkbTypes::Type::TriangleZ;
     579                 :            : 
     580                 :          0 :     case wkbPointM: return QgsWkbTypes::Type::PointM;
     581                 :          0 :     case wkbLineStringM: return QgsWkbTypes::Type::LineStringM;
     582                 :          0 :     case wkbPolygonM: return QgsWkbTypes::Type::PolygonM;
     583                 :          0 :     case wkbMultiPointM: return QgsWkbTypes::Type::MultiPointM;
     584                 :          0 :     case wkbMultiLineStringM: return QgsWkbTypes::Type::MultiLineStringM;
     585                 :          0 :     case wkbMultiPolygonM: return QgsWkbTypes::Type::MultiPolygonM;
     586                 :          0 :     case wkbGeometryCollectionM: return QgsWkbTypes::Type::GeometryCollectionM;
     587                 :          0 :     case wkbCircularStringM: return QgsWkbTypes::Type::CircularStringM;
     588                 :          0 :     case wkbCompoundCurveM: return QgsWkbTypes::Type::CompoundCurveM;
     589                 :          0 :     case wkbCurvePolygonM: return QgsWkbTypes::Type::CurvePolygonM;
     590                 :          0 :     case wkbMultiCurveM: return QgsWkbTypes::Type::MultiCurveM;
     591                 :          0 :     case wkbMultiSurfaceM: return QgsWkbTypes::Type::MultiSurfaceM;
     592                 :          0 :     case wkbCurveM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     593                 :          0 :     case wkbSurfaceM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     594                 :          0 :     case wkbPolyhedralSurfaceM: return QgsWkbTypes::Type::Unknown; // no actual matching
     595                 :          0 :     case wkbTINM: return QgsWkbTypes::Type::Unknown; // no actual matching
     596                 :          0 :     case wkbTriangleM: return QgsWkbTypes::Type::TriangleM;
     597                 :            : 
     598                 :          0 :     case wkbPointZM: return QgsWkbTypes::Type::PointZM;
     599                 :          0 :     case wkbLineStringZM: return QgsWkbTypes::Type::LineStringZM;
     600                 :          0 :     case wkbPolygonZM: return QgsWkbTypes::Type::PolygonZM;
     601                 :          0 :     case wkbMultiPointZM: return QgsWkbTypes::Type::MultiPointZM;
     602                 :          0 :     case wkbMultiLineStringZM: return QgsWkbTypes::Type::MultiLineStringZM;
     603                 :          0 :     case wkbMultiPolygonZM: return QgsWkbTypes::Type::MultiPolygonZM;
     604                 :          0 :     case wkbGeometryCollectionZM: return QgsWkbTypes::Type::GeometryCollectionZM;
     605                 :          0 :     case wkbCircularStringZM: return QgsWkbTypes::Type::CircularStringZM;
     606                 :          0 :     case wkbCompoundCurveZM: return QgsWkbTypes::Type::CompoundCurveZM;
     607                 :          0 :     case wkbCurvePolygonZM: return QgsWkbTypes::Type::CurvePolygonZM;
     608                 :          0 :     case wkbMultiCurveZM: return QgsWkbTypes::Type::MultiCurveZM;
     609                 :          0 :     case wkbMultiSurfaceZM: return QgsWkbTypes::Type::MultiSurfaceZM;
     610                 :          0 :     case wkbCurveZM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     611                 :          0 :     case wkbSurfaceZM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
     612                 :          0 :     case wkbPolyhedralSurfaceZM: return QgsWkbTypes::Type::Unknown; // no actual matching
     613                 :          0 :     case wkbTINZM: return QgsWkbTypes::Type::Unknown; // no actual matching
     614                 :          0 :     case wkbTriangleZM: return QgsWkbTypes::Type::TriangleZM;
     615                 :            : 
     616                 :          0 :     case wkbPoint25D: return QgsWkbTypes::Type::PointZ;
     617                 :          0 :     case wkbLineString25D: return QgsWkbTypes::Type::LineStringZ;
     618                 :          0 :     case wkbPolygon25D: return QgsWkbTypes::Type::PolygonZ;
     619                 :          0 :     case wkbMultiPoint25D: return QgsWkbTypes::Type::MultiPointZ;
     620                 :          0 :     case wkbMultiLineString25D: return QgsWkbTypes::Type::MultiLineStringZ;
     621                 :          0 :     case wkbMultiPolygon25D: return QgsWkbTypes::Type::MultiPolygonZ;
     622                 :          0 :     case wkbGeometryCollection25D: return QgsWkbTypes::Type::GeometryCollectionZ;
     623                 :            :   }
     624                 :            : 
     625                 :            :   // should not reach that point normally
     626                 :          0 :   return QgsWkbTypes::Type::Unknown;
     627                 :       1298 : }
     628                 :            : 
     629                 :       1101 : QgsGeometry QgsOgrUtils::ogrGeometryToQgsGeometry( OGRGeometryH geom )
     630                 :            : {
     631                 :       1101 :   if ( !geom )
     632                 :          0 :     return QgsGeometry();
     633                 :            : 
     634                 :       1101 :   const auto ogrGeomType = OGR_G_GetGeometryType( geom );
     635                 :       1101 :   QgsWkbTypes::Type wkbType = ogrGeometryTypeToQgsWkbType( ogrGeomType );
     636                 :            : 
     637                 :            :   // optimised case for some geometry classes, avoiding wkb conversion on OGR/QGIS sides
     638                 :            :   // TODO - extend to other classes!
     639                 :       1101 :   switch ( QgsWkbTypes::flatType( wkbType ) )
     640                 :            :   {
     641                 :            :     case QgsWkbTypes::Point:
     642                 :            :     {
     643                 :        133 :       return QgsGeometry( ogrGeometryToQgsPoint( geom ) );
     644                 :            :     }
     645                 :            : 
     646                 :            :     case QgsWkbTypes::MultiPoint:
     647                 :            :     {
     648                 :          0 :       return QgsGeometry( ogrGeometryToQgsMultiPoint( geom ) );
     649                 :            :     }
     650                 :            : 
     651                 :            :     case QgsWkbTypes::LineString:
     652                 :            :     {
     653                 :        153 :       return QgsGeometry( ogrGeometryToQgsLineString( geom ) );
     654                 :            :     }
     655                 :            : 
     656                 :            :     case QgsWkbTypes::MultiLineString:
     657                 :            :     {
     658                 :         21 :       return QgsGeometry( ogrGeometryToQgsMultiLineString( geom ) );
     659                 :            :     }
     660                 :            : 
     661                 :            :     case QgsWkbTypes::Polygon:
     662                 :            :     {
     663                 :        768 :       return QgsGeometry( ogrGeometryToQgsPolygon( geom ) );
     664                 :            :     }
     665                 :            : 
     666                 :            :     case QgsWkbTypes::MultiPolygon:
     667                 :            :     {
     668                 :         26 :       return QgsGeometry( ogrGeometryToQgsMultiPolygon( geom ) );
     669                 :            :     }
     670                 :            : 
     671                 :            :     default:
     672                 :          0 :       break;
     673                 :            :   }
     674                 :            : 
     675                 :            :   // Fallback to inefficient WKB conversions
     676                 :            : 
     677                 :          0 :   if ( wkbFlatten( wkbType ) == wkbGeometryCollection )
     678                 :            :   {
     679                 :            :     // Shapefile MultiPatch can be reported as GeometryCollectionZ of TINZ
     680                 :          0 :     if ( OGR_G_GetGeometryCount( geom ) >= 1 &&
     681                 :          0 :          wkbFlatten( OGR_G_GetGeometryType( OGR_G_GetGeometryRef( geom, 0 ) ) ) == wkbTIN )
     682                 :            :     {
     683                 :          0 :       auto newGeom = OGR_G_ForceToMultiPolygon( OGR_G_Clone( geom ) );
     684                 :          0 :       auto ret = ogrGeometryToQgsGeometry( newGeom );
     685                 :          0 :       OGR_G_DestroyGeometry( newGeom );
     686                 :          0 :       return ret;
     687                 :          0 :     }
     688                 :          0 :   }
     689                 :            : 
     690                 :            :   // get the wkb representation
     691                 :          0 :   int memorySize = OGR_G_WkbSize( geom );
     692                 :          0 :   unsigned char *wkb = new unsigned char[memorySize];
     693                 :          0 :   OGR_G_ExportToWkb( geom, static_cast<OGRwkbByteOrder>( QgsApplication::endian() ), wkb );
     694                 :            : 
     695                 :            :   // Read original geometry type
     696                 :            :   uint32_t origGeomType;
     697                 :          0 :   memcpy( &origGeomType, wkb + 1, sizeof( uint32_t ) );
     698                 :          0 :   bool hasZ = ( origGeomType >= 1000 && origGeomType < 2000 ) || ( origGeomType >= 3000 && origGeomType < 4000 );
     699                 :          0 :   bool hasM = ( origGeomType >= 2000 && origGeomType < 3000 ) || ( origGeomType >= 3000 && origGeomType < 4000 );
     700                 :            : 
     701                 :            :   // PolyhedralSurface and TINs are not supported, map them to multipolygons...
     702                 :          0 :   if ( origGeomType % 1000 == 16 ) // is TIN, TINZ, TINM or TINZM
     703                 :            :   {
     704                 :            :     // TIN has the same wkb layout as a multipolygon, just need to overwrite the geom types...
     705                 :          0 :     int nDims = 2 + hasZ + hasM;
     706                 :          0 :     uint32_t newMultiType = static_cast<uint32_t>( QgsWkbTypes::zmType( QgsWkbTypes::MultiPolygon, hasZ, hasM ) );
     707                 :          0 :     uint32_t newSingleType = static_cast<uint32_t>( QgsWkbTypes::zmType( QgsWkbTypes::Polygon, hasZ, hasM ) );
     708                 :          0 :     unsigned char *wkbptr = wkb;
     709                 :            : 
     710                 :            :     // Endianness
     711                 :          0 :     wkbptr += 1;
     712                 :            : 
     713                 :            :     // Overwrite geom type
     714                 :          0 :     memcpy( wkbptr, &newMultiType, sizeof( uint32_t ) );
     715                 :          0 :     wkbptr += 4;
     716                 :            : 
     717                 :            :     // Geom count
     718                 :            :     uint32_t numGeoms;
     719                 :          0 :     memcpy( &numGeoms, wkb + 5, sizeof( uint32_t ) );
     720                 :          0 :     wkbptr += 4;
     721                 :            : 
     722                 :            :     // For each part, overwrite the geometry type to polygon (Z|M)
     723                 :          0 :     for ( uint32_t i = 0; i < numGeoms; ++i )
     724                 :            :     {
     725                 :            :       // Endianness
     726                 :          0 :       wkbptr += 1;
     727                 :            : 
     728                 :            :       // Overwrite geom type
     729                 :          0 :       memcpy( wkbptr, &newSingleType, sizeof( uint32_t ) );
     730                 :          0 :       wkbptr += sizeof( uint32_t );
     731                 :            : 
     732                 :            :       // skip coordinates
     733                 :            :       uint32_t nRings;
     734                 :          0 :       memcpy( &nRings, wkbptr, sizeof( uint32_t ) );
     735                 :          0 :       wkbptr += sizeof( uint32_t );
     736                 :            : 
     737                 :          0 :       for ( uint32_t j = 0; j < nRings; ++j )
     738                 :            :       {
     739                 :            :         uint32_t nPoints;
     740                 :          0 :         memcpy( &nPoints, wkbptr, sizeof( uint32_t ) );
     741                 :          0 :         wkbptr += sizeof( uint32_t ) + sizeof( double ) * nDims * nPoints;
     742                 :          0 :       }
     743                 :          0 :     }
     744                 :          0 :   }
     745                 :          0 :   else if ( origGeomType % 1000 == 15 ) // PolyhedralSurface, PolyhedralSurfaceZ, PolyhedralSurfaceM or PolyhedralSurfaceZM
     746                 :            :   {
     747                 :            :     // PolyhedralSurface has the same wkb layout as a MultiPolygon, just need to overwrite the geom type...
     748                 :          0 :     uint32_t newType = static_cast<uint32_t>( QgsWkbTypes::zmType( QgsWkbTypes::MultiPolygon, hasZ, hasM ) );
     749                 :            :     // Overwrite geom type
     750                 :          0 :     memcpy( wkb + 1, &newType, sizeof( uint32_t ) );
     751                 :          0 :   }
     752                 :            : 
     753                 :          0 :   QgsGeometry g;
     754                 :          0 :   g.fromWkb( wkb, memorySize );
     755                 :          0 :   return g;
     756                 :       1101 : }
     757                 :            : 
     758                 :          0 : QgsFeatureList QgsOgrUtils::stringToFeatureList( const QString &string, const QgsFields &fields, QTextCodec *encoding )
     759                 :            : {
     760                 :          0 :   QgsFeatureList features;
     761                 :          0 :   if ( string.isEmpty() )
     762                 :          0 :     return features;
     763                 :            : 
     764                 :          0 :   QString randomFileName = QStringLiteral( "/vsimem/%1" ).arg( QUuid::createUuid().toString() );
     765                 :            : 
     766                 :            :   // create memory file system object from string buffer
     767                 :          0 :   QByteArray ba = string.toUtf8();
     768                 :          0 :   VSIFCloseL( VSIFileFromMemBuffer( randomFileName.toUtf8().constData(), reinterpret_cast< GByte * >( ba.data() ),
     769                 :          0 :                                     static_cast< vsi_l_offset >( ba.size() ), FALSE ) );
     770                 :            : 
     771                 :          0 :   gdal::ogr_datasource_unique_ptr hDS( OGROpen( randomFileName.toUtf8().constData(), false, nullptr ) );
     772                 :          0 :   if ( !hDS )
     773                 :            :   {
     774                 :          0 :     VSIUnlink( randomFileName.toUtf8().constData() );
     775                 :          0 :     return features;
     776                 :            :   }
     777                 :            : 
     778                 :          0 :   OGRLayerH ogrLayer = OGR_DS_GetLayer( hDS.get(), 0 );
     779                 :          0 :   if ( !ogrLayer )
     780                 :            :   {
     781                 :          0 :     hDS.reset();
     782                 :          0 :     VSIUnlink( randomFileName.toUtf8().constData() );
     783                 :          0 :     return features;
     784                 :            :   }
     785                 :            : 
     786                 :          0 :   gdal::ogr_feature_unique_ptr oFeat;
     787                 :          0 :   while ( oFeat.reset( OGR_L_GetNextFeature( ogrLayer ) ), oFeat )
     788                 :            :   {
     789                 :          0 :     QgsFeature feat = readOgrFeature( oFeat.get(), fields, encoding );
     790                 :          0 :     if ( feat.isValid() )
     791                 :          0 :       features << feat;
     792                 :          0 :   }
     793                 :            : 
     794                 :          0 :   hDS.reset();
     795                 :          0 :   VSIUnlink( randomFileName.toUtf8().constData() );
     796                 :            : 
     797                 :          0 :   return features;
     798                 :          0 : }
     799                 :            : 
     800                 :          0 : QgsFields QgsOgrUtils::stringToFields( const QString &string, QTextCodec *encoding )
     801                 :            : {
     802                 :          0 :   QgsFields fields;
     803                 :          0 :   if ( string.isEmpty() )
     804                 :          0 :     return fields;
     805                 :            : 
     806                 :          0 :   QString randomFileName = QStringLiteral( "/vsimem/%1" ).arg( QUuid::createUuid().toString() );
     807                 :            : 
     808                 :            :   // create memory file system object from buffer
     809                 :          0 :   QByteArray ba = string.toUtf8();
     810                 :          0 :   VSIFCloseL( VSIFileFromMemBuffer( randomFileName.toUtf8().constData(), reinterpret_cast< GByte * >( ba.data() ),
     811                 :          0 :                                     static_cast< vsi_l_offset >( ba.size() ), FALSE ) );
     812                 :            : 
     813                 :          0 :   gdal::ogr_datasource_unique_ptr hDS( OGROpen( randomFileName.toUtf8().constData(), false, nullptr ) );
     814                 :          0 :   if ( !hDS )
     815                 :            :   {
     816                 :          0 :     VSIUnlink( randomFileName.toUtf8().constData() );
     817                 :          0 :     return fields;
     818                 :            :   }
     819                 :            : 
     820                 :          0 :   OGRLayerH ogrLayer = OGR_DS_GetLayer( hDS.get(), 0 );
     821                 :          0 :   if ( !ogrLayer )
     822                 :            :   {
     823                 :          0 :     hDS.reset();
     824                 :          0 :     VSIUnlink( randomFileName.toUtf8().constData() );
     825                 :          0 :     return fields;
     826                 :            :   }
     827                 :            : 
     828                 :          0 :   gdal::ogr_feature_unique_ptr oFeat;
     829                 :            :   //read in the first feature only
     830                 :          0 :   if ( oFeat.reset( OGR_L_GetNextFeature( ogrLayer ) ), oFeat )
     831                 :            :   {
     832                 :          0 :     fields = readOgrFields( oFeat.get(), encoding );
     833                 :          0 :   }
     834                 :            : 
     835                 :          0 :   hDS.reset();
     836                 :          0 :   VSIUnlink( randomFileName.toUtf8().constData() );
     837                 :          0 :   return fields;
     838                 :          0 : }
     839                 :            : 
     840                 :          0 : QStringList QgsOgrUtils::cStringListToQStringList( char **stringList )
     841                 :            : {
     842                 :          0 :   QStringList strings;
     843                 :            : 
     844                 :            :   // presume null terminated string list
     845                 :          0 :   for ( qgssize i = 0; stringList[i]; ++i )
     846                 :            :   {
     847                 :          0 :     strings.append( QString::fromUtf8( stringList[i] ) );
     848                 :          0 :   }
     849                 :            : 
     850                 :          0 :   return strings;
     851                 :          0 : }
     852                 :            : 
     853                 :          3 : QString QgsOgrUtils::OGRSpatialReferenceToWkt( OGRSpatialReferenceH srs )
     854                 :            : {
     855                 :          3 :   if ( !srs )
     856                 :          0 :     return QString();
     857                 :            : 
     858                 :          3 :   char *pszWkt = nullptr;
     859                 :          6 :   const QByteArray multiLineOption = QStringLiteral( "MULTILINE=NO" ).toLocal8Bit();
     860                 :          6 :   const QByteArray formatOption = QStringLiteral( "FORMAT=WKT2" ).toLocal8Bit();
     861                 :          3 :   const char *const options[] = {multiLineOption.constData(), formatOption.constData(), nullptr};
     862                 :          3 :   OSRExportToWktEx( srs, &pszWkt, options );
     863                 :            : 
     864                 :          3 :   const QString res( pszWkt );
     865                 :          3 :   CPLFree( pszWkt );
     866                 :          3 :   return res;
     867                 :          3 : }
     868                 :            : 
     869                 :          3 : QgsCoordinateReferenceSystem QgsOgrUtils::OGRSpatialReferenceToCrs( OGRSpatialReferenceH srs )
     870                 :            : {
     871                 :          3 :   const QString wkt = OGRSpatialReferenceToWkt( srs );
     872                 :          3 :   if ( wkt.isEmpty() )
     873                 :          0 :     return QgsCoordinateReferenceSystem();
     874                 :            : 
     875                 :          3 :   return QgsCoordinateReferenceSystem::fromWkt( wkt );
     876                 :          3 : }
     877                 :            : 
     878                 :          0 : QString QgsOgrUtils::readShapefileEncoding( const QString &path )
     879                 :            : {
     880                 :          0 :   const QString cpgEncoding = readShapefileEncodingFromCpg( path );
     881                 :          0 :   if ( !cpgEncoding.isEmpty() )
     882                 :          0 :     return cpgEncoding;
     883                 :            : 
     884                 :          0 :   return readShapefileEncodingFromLdid( path );
     885                 :          0 : }
     886                 :            : 
     887                 :          0 : QString QgsOgrUtils::readShapefileEncodingFromCpg( const QString &path )
     888                 :            : {
     889                 :            : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,1,0)
     890                 :          0 :   QString errCause;
     891                 :          0 :   QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( path, false, QStringList(), 0, errCause, false );
     892                 :          0 :   return layer ? layer->GetMetadataItem( QStringLiteral( "ENCODING_FROM_CPG" ), QStringLiteral( "SHAPEFILE" ) ) : QString();
     893                 :            : #else
     894                 :            :   if ( !QFileInfo::exists( path ) )
     895                 :            :     return QString();
     896                 :            : 
     897                 :            :   // first try to read cpg file, if present
     898                 :            :   const QFileInfo fi( path );
     899                 :            :   const QString baseName = fi.completeBaseName();
     900                 :            :   const QString cpgPath = fi.dir().filePath( QStringLiteral( "%1.%2" ).arg( baseName, fi.suffix() == QLatin1String( "SHP" ) ? QStringLiteral( "CPG" ) : QStringLiteral( "cpg" ) ) );
     901                 :            :   if ( QFile::exists( cpgPath ) )
     902                 :            :   {
     903                 :            :     QFile cpgFile( cpgPath );
     904                 :            :     if ( cpgFile.open( QIODevice::ReadOnly ) )
     905                 :            :     {
     906                 :            :       QTextStream cpgStream( &cpgFile );
     907                 :            :       const QString cpgString = cpgStream.readLine();
     908                 :            :       cpgFile.close();
     909                 :            : 
     910                 :            :       if ( !cpgString.isEmpty() )
     911                 :            :       {
     912                 :            :         // from OGRShapeLayer::ConvertCodePage
     913                 :            :         // https://github.com/OSGeo/gdal/blob/master/gdal/ogr/ogrsf_frmts/shape/ogrshapelayer.cpp#L342
     914                 :            :         bool ok = false;
     915                 :            :         int cpgCodePage = cpgString.toInt( &ok );
     916                 :            :         if ( ok && ( ( cpgCodePage >= 437 && cpgCodePage <= 950 )
     917                 :            :                      || ( cpgCodePage >= 1250 && cpgCodePage <= 1258 ) ) )
     918                 :            :         {
     919                 :            :           return QStringLiteral( "CP%1" ).arg( cpgCodePage );
     920                 :            :         }
     921                 :            :         else if ( cpgString.startsWith( QLatin1String( "8859" ) ) )
     922                 :            :         {
     923                 :            :           if ( cpgString.length() > 4 && cpgString.at( 4 ) == '-' )
     924                 :            :             return QStringLiteral( "ISO-8859-%1" ).arg( cpgString.mid( 5 ) );
     925                 :            :           else
     926                 :            :             return QStringLiteral( "ISO-8859-%1" ).arg( cpgString.mid( 4 ) );
     927                 :            :         }
     928                 :            :         else if ( cpgString.startsWith( QLatin1String( "UTF-8" ), Qt::CaseInsensitive ) ||
     929                 :            :                   cpgString.startsWith( QLatin1String( "UTF8" ), Qt::CaseInsensitive ) )
     930                 :            :           return QStringLiteral( "UTF-8" );
     931                 :            :         else if ( cpgString.startsWith( QLatin1String( "ANSI 1251" ), Qt::CaseInsensitive ) )
     932                 :            :           return QStringLiteral( "CP1251" );
     933                 :            : 
     934                 :            :         return cpgString;
     935                 :            :       }
     936                 :            :     }
     937                 :            :   }
     938                 :            : 
     939                 :            :   return QString();
     940                 :            : #endif
     941                 :          0 : }
     942                 :            : 
     943                 :          0 : QString QgsOgrUtils::readShapefileEncodingFromLdid( const QString &path )
     944                 :            : {
     945                 :            : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,1,0)
     946                 :          0 :   QString errCause;
     947                 :          0 :   QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( path, false, QStringList(), 0, errCause, false );
     948                 :          0 :   return layer ? layer->GetMetadataItem( QStringLiteral( "ENCODING_FROM_LDID" ), QStringLiteral( "SHAPEFILE" ) ) : QString();
     949                 :            : #else
     950                 :            :   // from OGRShapeLayer::ConvertCodePage
     951                 :            :   // https://github.com/OSGeo/gdal/blob/master/gdal/ogr/ogrsf_frmts/shape/ogrshapelayer.cpp#L342
     952                 :            : 
     953                 :            :   if ( !QFileInfo::exists( path ) )
     954                 :            :     return QString();
     955                 :            : 
     956                 :            :   // first try to read cpg file, if present
     957                 :            :   const QFileInfo fi( path );
     958                 :            :   const QString baseName = fi.completeBaseName();
     959                 :            : 
     960                 :            :   // fallback to LDID value, read from DBF file
     961                 :            :   const QString dbfPath = fi.dir().filePath( QStringLiteral( "%1.%2" ).arg( baseName, fi.suffix() == QLatin1String( "SHP" ) ? QStringLiteral( "DBF" ) : QStringLiteral( "dbf" ) ) );
     962                 :            :   if ( QFile::exists( dbfPath ) )
     963                 :            :   {
     964                 :            :     QFile dbfFile( dbfPath );
     965                 :            :     if ( dbfFile.open( QIODevice::ReadOnly ) )
     966                 :            :     {
     967                 :            :       dbfFile.read( 29 );
     968                 :            :       QDataStream dbfIn( &dbfFile );
     969                 :            :       dbfIn.setByteOrder( QDataStream::LittleEndian );
     970                 :            :       quint8 ldid;
     971                 :            :       dbfIn >> ldid;
     972                 :            :       dbfFile.close();
     973                 :            : 
     974                 :            :       int nCP = -1;  // Windows code page.
     975                 :            : 
     976                 :            :       // http://www.autopark.ru/ASBProgrammerGuide/DBFSTRUC.HTM
     977                 :            :       switch ( ldid )
     978                 :            :       {
     979                 :            :         case 1: nCP = 437;      break;
     980                 :            :         case 2: nCP = 850;      break;
     981                 :            :         case 3: nCP = 1252;     break;
     982                 :            :         case 4: nCP = 10000;    break;
     983                 :            :         case 8: nCP = 865;      break;
     984                 :            :         case 10: nCP = 850;     break;
     985                 :            :         case 11: nCP = 437;     break;
     986                 :            :         case 13: nCP = 437;     break;
     987                 :            :         case 14: nCP = 850;     break;
     988                 :            :         case 15: nCP = 437;     break;
     989                 :            :         case 16: nCP = 850;     break;
     990                 :            :         case 17: nCP = 437;     break;
     991                 :            :         case 18: nCP = 850;     break;
     992                 :            :         case 19: nCP = 932;     break;
     993                 :            :         case 20: nCP = 850;     break;
     994                 :            :         case 21: nCP = 437;     break;
     995                 :            :         case 22: nCP = 850;     break;
     996                 :            :         case 23: nCP = 865;     break;
     997                 :            :         case 24: nCP = 437;     break;
     998                 :            :         case 25: nCP = 437;     break;
     999                 :            :         case 26: nCP = 850;     break;
    1000                 :            :         case 27: nCP = 437;     break;
    1001                 :            :         case 28: nCP = 863;     break;
    1002                 :            :         case 29: nCP = 850;     break;
    1003                 :            :         case 31: nCP = 852;     break;
    1004                 :            :         case 34: nCP = 852;     break;
    1005                 :            :         case 35: nCP = 852;     break;
    1006                 :            :         case 36: nCP = 860;     break;
    1007                 :            :         case 37: nCP = 850;     break;
    1008                 :            :         case 38: nCP = 866;     break;
    1009                 :            :         case 55: nCP = 850;     break;
    1010                 :            :         case 64: nCP = 852;     break;
    1011                 :            :         case 77: nCP = 936;     break;
    1012                 :            :         case 78: nCP = 949;     break;
    1013                 :            :         case 79: nCP = 950;     break;
    1014                 :            :         case 80: nCP = 874;     break;
    1015                 :            :         case 87: return QStringLiteral( "ISO-8859-1" );
    1016                 :            :         case 88: nCP = 1252;     break;
    1017                 :            :         case 89: nCP = 1252;     break;
    1018                 :            :         case 100: nCP = 852;     break;
    1019                 :            :         case 101: nCP = 866;     break;
    1020                 :            :         case 102: nCP = 865;     break;
    1021                 :            :         case 103: nCP = 861;     break;
    1022                 :            :         case 104: nCP = 895;     break;
    1023                 :            :         case 105: nCP = 620;     break;
    1024                 :            :         case 106: nCP = 737;     break;
    1025                 :            :         case 107: nCP = 857;     break;
    1026                 :            :         case 108: nCP = 863;     break;
    1027                 :            :         case 120: nCP = 950;     break;
    1028                 :            :         case 121: nCP = 949;     break;
    1029                 :            :         case 122: nCP = 936;     break;
    1030                 :            :         case 123: nCP = 932;     break;
    1031                 :            :         case 124: nCP = 874;     break;
    1032                 :            :         case 134: nCP = 737;     break;
    1033                 :            :         case 135: nCP = 852;     break;
    1034                 :            :         case 136: nCP = 857;     break;
    1035                 :            :         case 150: nCP = 10007;   break;
    1036                 :            :         case 151: nCP = 10029;   break;
    1037                 :            :         case 200: nCP = 1250;    break;
    1038                 :            :         case 201: nCP = 1251;    break;
    1039                 :            :         case 202: nCP = 1254;    break;
    1040                 :            :         case 203: nCP = 1253;    break;
    1041                 :            :         case 204: nCP = 1257;    break;
    1042                 :            :         default: break;
    1043                 :            :       }
    1044                 :            : 
    1045                 :            :       if ( nCP != -1 )
    1046                 :            :       {
    1047                 :            :         return QStringLiteral( "CP%1" ).arg( nCP );
    1048                 :            :       }
    1049                 :            :     }
    1050                 :            :   }
    1051                 :            :   return QString();
    1052                 :            : #endif
    1053                 :          0 : }
    1054                 :            : 
    1055                 :          0 : QVariantMap QgsOgrUtils::parseStyleString( const QString &string )
    1056                 :            : {
    1057                 :          0 :   QVariantMap styles;
    1058                 :            : 
    1059                 :          0 :   char **papszStyleString = CSLTokenizeString2( string.toUtf8().constData(), ";",
    1060                 :            :                             CSLT_HONOURSTRINGS
    1061                 :            :                             | CSLT_PRESERVEQUOTES
    1062                 :            :                             | CSLT_PRESERVEESCAPES );
    1063                 :          0 :   for ( int i = 0; papszStyleString[i] != nullptr; ++i )
    1064                 :            :   {
    1065                 :            :     // style string format is:
    1066                 :            :     // <tool_name>([<tool_param>[,<tool_param>[,...]]])
    1067                 :            : 
    1068                 :            :     // first extract tool name
    1069                 :          0 :     const thread_local QRegularExpression sToolPartRx( QStringLiteral( "^(.*?)\\((.*)\\)$" ) );
    1070                 :          0 :     const QString stylePart( papszStyleString[i] );
    1071                 :          0 :     const QRegularExpressionMatch match = sToolPartRx.match( stylePart );
    1072                 :          0 :     if ( !match.hasMatch() )
    1073                 :          0 :       continue;
    1074                 :            : 
    1075                 :          0 :     const QString tool = match.captured( 1 );
    1076                 :          0 :     const QString params = match.captured( 2 );
    1077                 :            : 
    1078                 :          0 :     char **papszTokens = CSLTokenizeString2( params.toUtf8().constData(), ",", CSLT_HONOURSTRINGS
    1079                 :            :                          | CSLT_PRESERVEESCAPES );
    1080                 :            : 
    1081                 :          0 :     QVariantMap toolParts;
    1082                 :          0 :     const thread_local QRegularExpression sToolParamRx( QStringLiteral( "^(.*?):(.*)$" ) );
    1083                 :          0 :     for ( int j = 0; papszTokens[j] != nullptr; ++j )
    1084                 :            :     {
    1085                 :          0 :       const QString toolPart( papszTokens[j] );
    1086                 :          0 :       const QRegularExpressionMatch toolMatch = sToolParamRx.match( toolPart );
    1087                 :          0 :       if ( !match.hasMatch() )
    1088                 :          0 :         continue;
    1089                 :            : 
    1090                 :            :       // note we always convert the keys to lowercase, just to be safe...
    1091                 :          0 :       toolParts.insert( toolMatch.captured( 1 ).toLower(), toolMatch.captured( 2 ) );
    1092                 :          0 :     }
    1093                 :          0 :     CSLDestroy( papszTokens );
    1094                 :            : 
    1095                 :            :     // note we always convert the keys to lowercase, just to be safe...
    1096                 :          0 :     styles.insert( tool.toLower(), toolParts );
    1097                 :          0 :   }
    1098                 :          0 :   CSLDestroy( papszStyleString );
    1099                 :          0 :   return styles;
    1100                 :          0 : }
    1101                 :            : 
    1102                 :          0 : std::unique_ptr<QgsSymbol> QgsOgrUtils::symbolFromStyleString( const QString &string, QgsSymbol::SymbolType type )
    1103                 :            : {
    1104                 :          0 :   const QVariantMap styles = parseStyleString( string );
    1105                 :            : 
    1106                 :          0 :   auto convertSize = []( const QString & size, double & value, QgsUnitTypes::RenderUnit & unit )->bool
    1107                 :            :   {
    1108                 :          0 :     const thread_local QRegularExpression sUnitRx = QRegularExpression( QStringLiteral( "^([\\d\\.]+)(g|px|pt|mm|cm|in)$" ) );
    1109                 :          0 :     const QRegularExpressionMatch match = sUnitRx.match( size );
    1110                 :          0 :     if ( match.hasMatch() )
    1111                 :            :     {
    1112                 :          0 :       value = match.captured( 1 ).toDouble();
    1113                 :          0 :       const QString unitString = match.captured( 2 );
    1114                 :          0 :       if ( unitString.compare( QLatin1String( "px" ), Qt::CaseInsensitive ) == 0 )
    1115                 :            :       {
    1116                 :            :         // pixels are a poor unit choice for QGIS -- they render badly in hidpi layouts. Convert to points instead, using
    1117                 :            :         // a 96 dpi conversion
    1118                 :            :         static constexpr double PT_TO_INCHES_FACTOR = 1 / 72.0;
    1119                 :            :         static constexpr double PX_TO_PT_FACTOR = 1 / ( 96.0 * PT_TO_INCHES_FACTOR );
    1120                 :          0 :         unit = QgsUnitTypes::RenderPoints;
    1121                 :          0 :         value *= PX_TO_PT_FACTOR;
    1122                 :          0 :         return true;
    1123                 :            :       }
    1124                 :          0 :       else if ( unitString.compare( QLatin1String( "pt" ), Qt::CaseInsensitive ) == 0 )
    1125                 :            :       {
    1126                 :          0 :         unit = QgsUnitTypes::RenderPoints;
    1127                 :          0 :         return true;
    1128                 :            :       }
    1129                 :          0 :       else if ( unitString.compare( QLatin1String( "mm" ), Qt::CaseInsensitive ) == 0 )
    1130                 :            :       {
    1131                 :          0 :         unit = QgsUnitTypes::RenderMillimeters;
    1132                 :          0 :         return true;
    1133                 :            :       }
    1134                 :          0 :       else if ( unitString.compare( QLatin1String( "cm" ), Qt::CaseInsensitive ) == 0 )
    1135                 :            :       {
    1136                 :          0 :         value *= 10;
    1137                 :          0 :         unit = QgsUnitTypes::RenderMillimeters;
    1138                 :          0 :         return true;
    1139                 :            :       }
    1140                 :          0 :       else if ( unitString.compare( QLatin1String( "in" ), Qt::CaseInsensitive ) == 0 )
    1141                 :            :       {
    1142                 :          0 :         unit = QgsUnitTypes::RenderInches;
    1143                 :          0 :         return true;
    1144                 :            :       }
    1145                 :          0 :       else if ( unitString.compare( QLatin1String( "g" ), Qt::CaseInsensitive ) == 0 )
    1146                 :            :       {
    1147                 :          0 :         unit = QgsUnitTypes::RenderMapUnits;
    1148                 :          0 :         return true;
    1149                 :            :       }
    1150                 :          0 :       QgsDebugMsg( QStringLiteral( "Unknown unit %1" ).arg( unitString ) );
    1151                 :          0 :     }
    1152                 :            :     else
    1153                 :            :     {
    1154                 :          0 :       QgsDebugMsg( QStringLiteral( "Could not parse style size %1" ).arg( size ) );
    1155                 :            :     }
    1156                 :          0 :     return false;
    1157                 :          0 :   };
    1158                 :            : 
    1159                 :          0 :   auto convertColor = []( const QString & string ) -> QColor
    1160                 :            :   {
    1161                 :          0 :     const thread_local QRegularExpression sColorWithAlphaRx = QRegularExpression( QStringLiteral( "^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})$" ) );
    1162                 :          0 :     const QRegularExpressionMatch match = sColorWithAlphaRx.match( string );
    1163                 :          0 :     if ( match.hasMatch() )
    1164                 :            :     {
    1165                 :            :       // need to convert #RRGGBBAA to #AARRGGBB for QColor
    1166                 :          0 :       return QColor( QStringLiteral( "#%1%2" ).arg( match.captured( 2 ), match.captured( 1 ) ) );
    1167                 :            :     }
    1168                 :            :     else
    1169                 :            :     {
    1170                 :          0 :       return QColor( string );
    1171                 :            :     }
    1172                 :          0 :   };
    1173                 :            : 
    1174                 :          0 :   if ( type == QgsSymbol::Line && styles.contains( QStringLiteral( "pen" ) ) )
    1175                 :            :   {
    1176                 :            :     // line symbol type
    1177                 :          0 :     const QVariantMap lineStyle = styles.value( QStringLiteral( "pen" ) ).toMap();
    1178                 :          0 :     QColor color = convertColor( lineStyle.value( QStringLiteral( "c" ), QStringLiteral( "#000000" ) ).toString() );
    1179                 :            : 
    1180                 :          0 :     double lineWidth = DEFAULT_SIMPLELINE_WIDTH;
    1181                 :          0 :     QgsUnitTypes::RenderUnit lineWidthUnit = QgsUnitTypes::RenderMillimeters;
    1182                 :          0 :     convertSize( lineStyle.value( QStringLiteral( "w" ) ).toString(), lineWidth, lineWidthUnit );
    1183                 :            : 
    1184                 :          0 :     std::unique_ptr< QgsSimpleLineSymbolLayer > simpleLine = std::make_unique< QgsSimpleLineSymbolLayer >( color, lineWidth );
    1185                 :          0 :     simpleLine->setWidthUnit( lineWidthUnit );
    1186                 :            : 
    1187                 :            :     // pattern
    1188                 :          0 :     const QString pattern = lineStyle.value( QStringLiteral( "p" ) ).toString();
    1189                 :          0 :     if ( !pattern.isEmpty() )
    1190                 :            :     {
    1191                 :          0 :       const thread_local QRegularExpression sPatternUnitRx = QRegularExpression( QStringLiteral( "^([\\d\\.\\s]+)(g|px|pt|mm|cm|in)$" ) );
    1192                 :          0 :       const QRegularExpressionMatch match = sPatternUnitRx.match( pattern );
    1193                 :          0 :       if ( match.hasMatch() )
    1194                 :            :       {
    1195                 :          0 :         const QStringList patternValues = match.captured( 1 ).split( ' ' );
    1196                 :          0 :         QVector< qreal > dashPattern;
    1197                 :          0 :         QgsUnitTypes::RenderUnit patternUnits = QgsUnitTypes::RenderMillimeters;
    1198                 :          0 :         for ( const QString &val : patternValues )
    1199                 :            :         {
    1200                 :            :           double length;
    1201                 :          0 :           convertSize( val + match.captured( 2 ), length, patternUnits );
    1202                 :          0 :           dashPattern.push_back( length * lineWidth * 2 );
    1203                 :            :         }
    1204                 :            : 
    1205                 :          0 :         simpleLine->setCustomDashVector( dashPattern );
    1206                 :          0 :         simpleLine->setCustomDashPatternUnit( patternUnits );
    1207                 :          0 :         simpleLine->setUseCustomDashPattern( true );
    1208                 :          0 :       }
    1209                 :          0 :     }
    1210                 :            : 
    1211                 :          0 :     Qt::PenCapStyle capStyle = Qt::FlatCap;
    1212                 :          0 :     Qt::PenJoinStyle joinStyle = Qt::MiterJoin;
    1213                 :            :     // workaround https://github.com/OSGeo/gdal/pull/3509 in older GDAL versions
    1214                 :          0 :     const QString id = lineStyle.value( QStringLiteral( "id" ) ).toString();
    1215                 :          0 :     if ( id.contains( QLatin1String( "mapinfo-pen" ), Qt::CaseInsensitive ) )
    1216                 :            :     {
    1217                 :            :       // MapInfo renders all lines using a round pen cap and round pen join
    1218                 :            :       // which are not the default values for OGR pen cap/join styles. So we need to explicitly
    1219                 :            :       // override the OGR default values here on older GDAL versions
    1220                 :          0 :       capStyle = Qt::RoundCap;
    1221                 :          0 :       joinStyle = Qt::RoundJoin;
    1222                 :          0 :     }
    1223                 :            : 
    1224                 :            :     // pen cap
    1225                 :          0 :     const QString penCap = lineStyle.value( QStringLiteral( "cap" ) ).toString();
    1226                 :          0 :     if ( penCap.compare( QLatin1String( "b" ), Qt::CaseInsensitive ) == 0 )
    1227                 :            :     {
    1228                 :          0 :       capStyle = Qt::FlatCap;
    1229                 :          0 :     }
    1230                 :          0 :     else if ( penCap.compare( QLatin1String( "r" ), Qt::CaseInsensitive ) == 0 )
    1231                 :            :     {
    1232                 :          0 :       capStyle = Qt::RoundCap;
    1233                 :          0 :     }
    1234                 :          0 :     else if ( penCap.compare( QLatin1String( "p" ), Qt::CaseInsensitive ) == 0 )
    1235                 :            :     {
    1236                 :          0 :       capStyle = Qt::SquareCap;
    1237                 :          0 :     }
    1238                 :          0 :     simpleLine->setPenCapStyle( capStyle );
    1239                 :            : 
    1240                 :            :     // pen join
    1241                 :          0 :     const QString penJoin = lineStyle.value( QStringLiteral( "j" ) ).toString();
    1242                 :          0 :     if ( penJoin.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0 )
    1243                 :            :     {
    1244                 :          0 :       joinStyle = Qt::MiterJoin;
    1245                 :          0 :     }
    1246                 :          0 :     else if ( penJoin.compare( QLatin1String( "r" ), Qt::CaseInsensitive ) == 0 )
    1247                 :            :     {
    1248                 :          0 :       joinStyle = Qt::RoundJoin;
    1249                 :          0 :     }
    1250                 :          0 :     else if ( penJoin.compare( QLatin1String( "b" ), Qt::CaseInsensitive ) == 0 )
    1251                 :            :     {
    1252                 :          0 :       joinStyle = Qt::BevelJoin;
    1253                 :          0 :     }
    1254                 :          0 :     simpleLine->setPenJoinStyle( joinStyle );
    1255                 :            : 
    1256                 :          0 :     const QString priority = lineStyle.value( QStringLiteral( "l" ) ).toString();
    1257                 :          0 :     if ( !priority.isEmpty() )
    1258                 :            :     {
    1259                 :          0 :       simpleLine->setRenderingPass( priority.toInt() );
    1260                 :          0 :     }
    1261                 :          0 :     return std::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << simpleLine.release() );
    1262                 :          0 :   }
    1263                 :          0 :   return nullptr;
    1264                 :          0 : }

Generated by: LCOV version 1.14