QGIS API Documentation 3.37.0-Master (684a802617f)
Loading...
Searching...
No Matches
qgsogrutils.cpp
Go to the documentation of this file.
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 "qgslinesymbollayer.h"
25#include "qgspolygon.h"
26#include "qgsmultipolygon.h"
28#include "qgsfillsymbollayer.h"
30#include "qgssymbollayerutils.h"
31#include "qgsfontutils.h"
32#include "qgsmessagelog.h"
33#include "qgssymbol.h"
34#include "qgsfillsymbol.h"
35#include "qgslinesymbol.h"
36#include "qgsmarkersymbol.h"
37#include "qgsfielddomain.h"
38#include "qgsfontmanager.h"
39#include "qgsvariantutils.h"
40#include "qgsogrproviderutils.h"
41
42#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
43#include "qgsweakrelation.h"
44#include "qgsproviderregistry.h"
45#include "qgsprovidermetadata.h"
46#endif
47
48#include <cmath>
49#include <limits>
50#include <QTextCodec>
51#include <QUuid>
52#include <cpl_error.h>
53#include <QJsonDocument>
54#include <QFileInfo>
55#include <QDir>
56#include <QTextStream>
57#include <QDataStream>
58#include <QRegularExpression>
59
60#include "ogr_srs_api.h"
61
62
63void gdal::OGRDataSourceDeleter::operator()( OGRDataSourceH source ) const
64{
65 OGR_DS_Destroy( source );
66}
67
68
69void gdal::OGRGeometryDeleter::operator()( OGRGeometryH geometry ) const
70{
71 OGR_G_DestroyGeometry( geometry );
72}
73
74void gdal::OGRFldDeleter::operator()( OGRFieldDefnH definition ) const
75{
76 OGR_Fld_Destroy( definition );
77}
78
79void gdal::OGRFeatureDeleter::operator()( OGRFeatureH feature ) const
80{
81 OGR_F_Destroy( feature );
82}
83
85{
86 GDALClose( dataset );
87}
88
89void gdal::fast_delete_and_close( gdal::dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path )
90{
91 // see https://github.com/qgis/QGIS/commit/d024910490a39e65e671f2055c5b6543e06c7042#commitcomment-25194282
92 // faster if we close the handle AFTER delete, but doesn't work for windows
93#ifdef Q_OS_WIN
94 // close dataset handle
95 dataset.reset();
96#endif
97
98 CPLPushErrorHandler( CPLQuietErrorHandler );
99 GDALDeleteDataset( driver, path.toUtf8().constData() );
100 CPLPopErrorHandler();
101
102#ifndef Q_OS_WIN
103 // close dataset handle
104 dataset.reset();
105#endif
106}
107
108
109void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options ) const
110{
111 GDALDestroyWarpOptions( options );
112}
113
114#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
115void gdal::GDALRelationshipDeleter::operator()( GDALRelationshipH relationship ) const
116{
117 GDALDestroyRelationship( relationship );
118}
119#endif
120
121static void setQTTimeZoneFromOGRTZFlag( QDateTime &dt, int nTZFlag )
122{
123 // Take into account time zone
124 if ( nTZFlag == 0 )
125 {
126 // unknown time zone
127 }
128 else if ( nTZFlag == 1 )
129 {
130 dt.setTimeSpec( Qt::LocalTime );
131 }
132 else if ( nTZFlag == 100 )
133 {
134 dt.setTimeSpec( Qt::UTC );
135 }
136 else
137 {
138 // TZFlag = 101 ==> UTC+00:15
139 // TZFlag = 99 ==> UTC-00:15
140 dt.setOffsetFromUtc( ( nTZFlag - 100 ) * 15 * 60 );
141 }
142}
143
144QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType type )
145{
146 if ( !value || OGR_RawField_IsUnset( value ) || OGR_RawField_IsNull( value ) )
147 return QVariant();
148
149 switch ( type )
150 {
151 case OFTInteger:
152 return value->Integer;
153
154 case OFTInteger64:
155 return value->Integer64;
156
157 case OFTReal:
158 return value->Real;
159
160 case OFTString:
161 case OFTWideString:
162 return QString::fromUtf8( value->String );
163
164 case OFTDate:
165 return QDate( value->Date.Year, value->Date.Month, value->Date.Day );
166
167 case OFTTime:
168 {
169 float secondsPart = 0;
170 float millisecondPart = std::modf( value->Date.Second, &secondsPart );
171 return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
172 }
173
174 case OFTDateTime:
175 {
176 float secondsPart = 0;
177 float millisecondPart = std::modf( value->Date.Second, &secondsPart );
178 QDateTime dt = QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
179 QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
180 setQTTimeZoneFromOGRTZFlag( dt, value->Date.TZFlag );
181 return dt;
182 }
183
184 case OFTBinary:
185 // not supported!
186 Q_ASSERT_X( false, "QgsOgrUtils::OGRFieldtoVariant", "OFTBinary type not supported" );
187 return QVariant();
188
189 case OFTIntegerList:
190 {
191 QVariantList res;
192 res.reserve( value->IntegerList.nCount );
193 for ( int i = 0; i < value->IntegerList.nCount; ++i )
194 res << value->IntegerList.paList[ i ];
195 return res;
196 }
197
198 case OFTInteger64List:
199 {
200 QVariantList res;
201 res.reserve( value->Integer64List.nCount );
202 for ( int i = 0; i < value->Integer64List.nCount; ++i )
203 res << value->Integer64List.paList[ i ];
204 return res;
205 }
206
207 case OFTRealList:
208 {
209 QVariantList res;
210 res.reserve( value->RealList.nCount );
211 for ( int i = 0; i < value->RealList.nCount; ++i )
212 res << value->RealList.paList[ i ];
213 return res;
214 }
215
216 case OFTStringList:
217 case OFTWideStringList:
218 {
219 QVariantList res;
220 res.reserve( value->StringList.nCount );
221 for ( int i = 0; i < value->StringList.nCount; ++i )
222 res << QString::fromUtf8( value->StringList.paList[ i ] );
223 return res;
224 }
225 }
226 return QVariant();
227}
228
229int QgsOgrUtils::OGRTZFlagFromQt( const QDateTime &datetime )
230{
231 if ( datetime.timeSpec() == Qt::LocalTime )
232 return 1;
233 return 100 + datetime.offsetFromUtc() / ( 60 * 15 );
234}
235
236std::unique_ptr< OGRField > QgsOgrUtils::variantToOGRField( const QVariant &value, OGRFieldType type )
237{
238 std::unique_ptr< OGRField > res = std::make_unique< OGRField >();
239
240 switch ( value.userType() )
241 {
242 case QMetaType::Type::UnknownType:
243 OGR_RawField_SetUnset( res.get() );
244 break;
245 case QMetaType::Type::Bool:
246 {
247 const int val = value.toBool() ? 1 : 0;
248 if ( type == OFTInteger )
249 res->Integer = val;
250 else if ( type == OFTInteger64 )
251 res->Integer64 = val;
252 else if ( type == OFTReal )
253 res->Real = val;
254 else
255 {
256 QgsDebugError( "Unsupported output data type for Bool" );
257 return nullptr;
258 }
259 break;
260 }
261 case QMetaType::Type::Int:
262 {
263 const int val = value.toInt();
264 if ( type == OFTInteger )
265 res->Integer = val;
266 else if ( type == OFTInteger64 )
267 res->Integer64 = val;
268 else if ( type == OFTReal )
269 res->Real = val;
270 else
271 {
272 QgsDebugError( "Unsupported output data type for Int" );
273 return nullptr;
274 }
275 break;
276 }
277 case QMetaType::Type::LongLong:
278 {
279 const qint64 val = value.toLongLong();
280 if ( type == OFTInteger )
281 {
282 if ( val <= std::numeric_limits<int>::max() &&
283 val >= std::numeric_limits<int>::min() )
284 {
285 res->Integer = static_cast<int>( val );
286 }
287 else
288 {
289 QgsDebugError( "Value does not fit on Integer" );
290 return nullptr;
291 }
292 }
293 else if ( type == OFTInteger64 )
294 res->Integer64 = val;
295 else if ( type == OFTReal )
296 {
297 res->Real = static_cast<double>( val );
298 }
299 else
300 {
301 QgsDebugError( "Unsupported output data type for LongLong" );
302 return nullptr;
303 }
304 break;
305 }
306 case QMetaType::Type::Double:
307 {
308 double val = value.toDouble();
309 if ( type == OFTInteger )
310 {
311 if ( val <= std::numeric_limits<int>::max() &&
312 val >= std::numeric_limits<int>::min() )
313 {
314 res->Integer = static_cast<int>( val );
315 }
316 else
317 {
318 QgsDebugError( "Value does not fit on Integer" );
319 return nullptr;
320 }
321 }
322 else if ( type == OFTInteger64 )
323 {
324 if ( val <= static_cast<double>( std::numeric_limits<qint64>::max() ) &&
325 val >= static_cast<double>( std::numeric_limits<qint64>::min() ) )
326 {
327 res->Integer64 = static_cast<qint64>( val );
328 }
329 else
330 {
331 QgsDebugError( "Value does not fit on Integer64" );
332 return nullptr;
333 }
334 }
335 else if ( type == OFTReal )
336 {
337 res->Real = val;
338 }
339 else
340 {
341 QgsDebugError( "Unsupported output data type for LongLong" );
342 return nullptr;
343 }
344 break;
345 }
346 case QMetaType::Type::QChar:
347 case QMetaType::Type::QString:
348 {
349 if ( type == OFTString )
350 res->String = CPLStrdup( value.toString().toUtf8().constData() );
351 else
352 {
353 QgsDebugError( "Unsupported output data type for String" );
354 return nullptr;
355 }
356 break;
357 }
358 case QMetaType::Type::QDate:
359 {
360 if ( type == OFTDate )
361 {
362 const QDate date = value.toDate();
363 res->Date.Day = date.day();
364 res->Date.Month = date.month();
365 res->Date.Year = static_cast<GInt16>( date.year() );
366 res->Date.TZFlag = 0;
367 }
368 else
369 {
370 QgsDebugError( "Unsupported output data type for Date" );
371 return nullptr;
372 }
373 break;
374 }
375 case QMetaType::Type::QTime:
376 {
377 if ( type == OFTTime )
378 {
379 const QTime time = value.toTime();
380 res->Date.Hour = time.hour();
381 res->Date.Minute = time.minute();
382 res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
383 res->Date.TZFlag = 0;
384 }
385 else
386 {
387 QgsDebugError( "Unsupported output data type for Time" );
388 return nullptr;
389 }
390 break;
391 }
392 case QMetaType::Type::QDateTime:
393 {
394 if ( type == OFTDateTime )
395 {
396 const QDateTime dt = value.toDateTime();
397 const QDate date = dt.date();
398 res->Date.Day = date.day();
399 res->Date.Month = date.month();
400 res->Date.Year = static_cast<GInt16>( date.year() );
401 const QTime time = dt.time();
402 res->Date.Hour = time.hour();
403 res->Date.Minute = time.minute();
404 res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
405 res->Date.TZFlag = OGRTZFlagFromQt( dt );
406 }
407 else
408 {
409 QgsDebugError( "Unsupported output data type for DateTime" );
410 return nullptr;
411 }
412 break;
413 }
414
415 default:
416 QgsDebugError( "Unhandled variant type in variantToOGRField" );
417 OGR_RawField_SetUnset( res.get() );
418 break;
419 }
420
421 return res;
422}
423
424QgsFeature QgsOgrUtils::readOgrFeature( OGRFeatureH ogrFet, const QgsFields &fields, QTextCodec *encoding )
425{
426 QgsFeature feature;
427 if ( !ogrFet )
428 {
429 feature.setValid( false );
430 return feature;
431 }
432
433 feature.setId( OGR_F_GetFID( ogrFet ) );
434 feature.setValid( true );
435
436 if ( !readOgrFeatureGeometry( ogrFet, feature ) )
437 {
438 feature.setValid( false );
439 }
440
441 if ( !readOgrFeatureAttributes( ogrFet, fields, feature, encoding ) )
442 {
443 feature.setValid( false );
444 }
445
446 return feature;
447}
448
449QgsFields QgsOgrUtils::readOgrFields( OGRFeatureH ogrFet, QTextCodec *encoding )
450{
451 QgsFields fields;
452
453 if ( !ogrFet )
454 return fields;
455
456 int fieldCount = OGR_F_GetFieldCount( ogrFet );
457 for ( int i = 0; i < fieldCount; ++i )
458 {
459 OGRFieldDefnH fldDef = OGR_F_GetFieldDefnRef( ogrFet, i );
460 if ( !fldDef )
461 {
462 fields.append( QgsField() );
463 continue;
464 }
465
466 QString name = encoding ? encoding->toUnicode( OGR_Fld_GetNameRef( fldDef ) ) : QString::fromUtf8( OGR_Fld_GetNameRef( fldDef ) );
467 QMetaType::Type varType;
468 switch ( OGR_Fld_GetType( fldDef ) )
469 {
470 case OFTInteger:
471 if ( OGR_Fld_GetSubType( fldDef ) == OFSTBoolean )
472 varType = QMetaType::Type::Bool;
473 else
474 varType = QMetaType::Type::Int;
475 break;
476 case OFTInteger64:
477 varType = QMetaType::Type::LongLong;
478 break;
479 case OFTReal:
480 varType = QMetaType::Type::Double;
481 break;
482 case OFTDate:
483 varType = QMetaType::Type::QDate;
484 break;
485 case OFTTime:
486 varType = QMetaType::Type::QTime;
487 break;
488 case OFTDateTime:
489 varType = QMetaType::Type::QDateTime;
490 break;
491 case OFTString:
492 if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON )
493 varType = QMetaType::Type::QVariantMap;
494 else
495 varType = QMetaType::Type::QString;
496 break;
497 default:
498 varType = QMetaType::Type::QString; // other unsupported, leave it as a string
499 }
500 fields.append( QgsField( name, varType ) );
501 }
502 return fields;
503}
504
505
506QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsFields &fields, int attIndex, QTextCodec *encoding, bool *ok )
507{
508 if ( attIndex < 0 || attIndex >= fields.count() )
509 {
510 if ( ok )
511 *ok = false;
512 return QVariant();
513 }
514
515 const QgsField field = fields.at( attIndex );
516 return getOgrFeatureAttribute( ogrFet, field, attIndex, encoding, ok );
517}
518
519QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField &field, int attIndex, QTextCodec *encoding, bool *ok )
520{
521 if ( !ogrFet || attIndex < 0 )
522 {
523 if ( ok )
524 *ok = false;
525 return QVariant();
526 }
527
528 OGRFieldDefnH fldDef = OGR_F_GetFieldDefnRef( ogrFet, attIndex );
529
530 if ( ! fldDef )
531 {
532 if ( ok )
533 *ok = false;
534
535 QgsDebugError( QStringLiteral( "ogrFet->GetFieldDefnRef(attindex) returns NULL" ) );
536 return QVariant();
537 }
538
539 QVariant value;
540
541 if ( ok )
542 *ok = true;
543
544 if ( OGR_F_IsFieldSetAndNotNull( ogrFet, attIndex ) )
545 {
546 switch ( field.type() )
547 {
548 case QMetaType::Type::QString:
549 {
550 if ( encoding )
551 value = QVariant( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
552 else
553 value = QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
554
555#ifdef Q_OS_WIN
556 // Fixes GH #41076 (empty strings shown as NULL), because we have checked before that it was NOT NULL
557 // Note: QVariant( QString( ) ).isNull( ) is still true on windows so we really need string literal :(
558 if ( value.isNull() )
559 value = QVariant( QStringLiteral( "" ) ); // skip-keyword-check
560#endif
561
562 break;
563 }
564 case QMetaType::Type::Int:
565 value = QVariant( OGR_F_GetFieldAsInteger( ogrFet, attIndex ) );
566 break;
567 case QMetaType::Type::Bool:
568 value = QVariant( bool( OGR_F_GetFieldAsInteger( ogrFet, attIndex ) ) );
569 break;
570 case QMetaType::Type::LongLong:
571 value = QVariant( OGR_F_GetFieldAsInteger64( ogrFet, attIndex ) );
572 break;
573 case QMetaType::Type::Double:
574 value = QVariant( OGR_F_GetFieldAsDouble( ogrFet, attIndex ) );
575 break;
576 case QMetaType::Type::QDate:
577 case QMetaType::Type::QDateTime:
578 case QMetaType::Type::QTime:
579 {
580 int year, month, day, hour, minute, tzf;
581 float second;
582 float secondsPart = 0;
583
584 OGR_F_GetFieldAsDateTimeEx( ogrFet, attIndex, &year, &month, &day, &hour, &minute, &second, &tzf );
585 float millisecondPart = std::modf( second, &secondsPart );
586
587 if ( field.type() == QMetaType::Type::QDate )
588 value = QDate( year, month, day );
589 else if ( field.type() == QMetaType::Type::QTime )
590 value = QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
591 else
592 {
593 QDateTime dt = QDateTime( QDate( year, month, day ),
594 QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
595 setQTTimeZoneFromOGRTZFlag( dt, tzf );
596 value = dt;
597 }
598 }
599 break;
600
601 case QMetaType::Type::QByteArray:
602 {
603 int size = 0;
604 const GByte *b = OGR_F_GetFieldAsBinary( ogrFet, attIndex, &size );
605
606 // QByteArray::fromRawData is funny. It doesn't take ownership of the data, so we have to explicitly call
607 // detach on it to force a copy which owns the data
608 QByteArray ba = QByteArray::fromRawData( reinterpret_cast<const char *>( b ), size );
609 ba.detach();
610
611 value = ba;
612 break;
613 }
614
615 case QMetaType::Type::QStringList:
616 {
617 QStringList list;
618 char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
619 const int count = CSLCount( lst );
620 if ( count > 0 )
621 {
622 list.reserve( count );
623 for ( int i = 0; i < count; i++ )
624 {
625 if ( encoding )
626 list << encoding->toUnicode( lst[i] );
627 else
628 list << QString::fromUtf8( lst[i] );
629 }
630 }
631 value = list;
632 break;
633 }
634
635 case QMetaType::Type::QVariantList:
636 {
637 switch ( field.subType() )
638 {
639 case QMetaType::Type::QString:
640 {
641 QStringList list;
642 char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
643 const int count = CSLCount( lst );
644 if ( count > 0 )
645 {
646 list.reserve( count );
647 for ( int i = 0; i < count; i++ )
648 {
649 if ( encoding )
650 list << encoding->toUnicode( lst[i] );
651 else
652 list << QString::fromUtf8( lst[i] );
653 }
654 }
655 value = list;
656 break;
657 }
658
659 case QMetaType::Type::Int:
660 {
661 QVariantList list;
662 int count = 0;
663 const int *lst = OGR_F_GetFieldAsIntegerList( ogrFet, attIndex, &count );
664 if ( count > 0 )
665 {
666 list.reserve( count );
667 for ( int i = 0; i < count; i++ )
668 {
669 list << lst[i];
670 }
671 }
672 value = list;
673 break;
674 }
675
676 case QMetaType::Type::Double:
677 {
678 QVariantList list;
679 int count = 0;
680 const double *lst = OGR_F_GetFieldAsDoubleList( ogrFet, attIndex, &count );
681 if ( count > 0 )
682 {
683 list.reserve( count );
684 for ( int i = 0; i < count; i++ )
685 {
686 list << lst[i];
687 }
688 }
689 value = list;
690 break;
691 }
692
693 case QMetaType::Type::LongLong:
694 {
695 QVariantList list;
696 int count = 0;
697 const long long *lst = OGR_F_GetFieldAsInteger64List( ogrFet, attIndex, &count );
698 if ( count > 0 )
699 {
700 list.reserve( count );
701 for ( int i = 0; i < count; i++ )
702 {
703 list << lst[i];
704 }
705 }
706 value = list;
707 break;
708 }
709
710 default:
711 {
712 Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
713 if ( ok )
714 *ok = false;
715 break;
716 }
717 }
718 break;
719 }
720
721 case QMetaType::Type::QVariantMap:
722 {
723 //it has to be JSON
724 //it's null if no json format
725 if ( encoding )
726 value = QJsonDocument::fromJson( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant();
727 else
728 value = QJsonDocument::fromJson( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant();
729 break;
730 }
731 default:
732 Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
733 if ( ok )
734 *ok = false;
735 }
736 }
737 else
738 {
739 value = QgsVariantUtils::createNullVariant( field.type() );
740 }
741
742 return value;
743}
744
745bool QgsOgrUtils::readOgrFeatureAttributes( OGRFeatureH ogrFet, const QgsFields &fields, QgsFeature &feature, QTextCodec *encoding )
746{
747 // read all attributes
748 feature.initAttributes( fields.count() );
749 feature.setFields( fields );
750
751 if ( !ogrFet )
752 return false;
753
754 bool ok = false;
755 for ( int idx = 0; idx < fields.count(); ++idx )
756 {
757 QVariant value = getOgrFeatureAttribute( ogrFet, fields, idx, encoding, &ok );
758 if ( ok )
759 {
760 feature.setAttribute( idx, value );
761 }
762 }
763 return true;
764}
765
766bool QgsOgrUtils::readOgrFeatureGeometry( OGRFeatureH ogrFet, QgsFeature &feature )
767{
768 if ( !ogrFet )
769 return false;
770
771 OGRGeometryH geom = OGR_F_GetGeometryRef( ogrFet );
772 if ( !geom )
773 feature.clearGeometry();
774 else
775 feature.setGeometry( ogrGeometryToQgsGeometry( geom ) );
776
777 return true;
778}
779
780std::unique_ptr< QgsPoint > ogrGeometryToQgsPoint( OGRGeometryH geom )
781{
782 Qgis::WkbType wkbType = QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGR_G_GetGeometryType( geom ) );
783
784 double x, y, z, m;
785 OGR_G_GetPointZM( geom, 0, &x, &y, &z, &m );
786 return std::make_unique< QgsPoint >( wkbType, x, y, z, m );
787}
788
789std::unique_ptr< QgsMultiPoint > ogrGeometryToQgsMultiPoint( OGRGeometryH geom )
790{
791 std::unique_ptr< QgsMultiPoint > mp = std::make_unique< QgsMultiPoint >();
792
793 const int count = OGR_G_GetGeometryCount( geom );
794 mp->reserve( count );
795 for ( int i = 0; i < count; ++i )
796 {
797 mp->addGeometry( ogrGeometryToQgsPoint( OGR_G_GetGeometryRef( geom, i ) ).release() );
798 }
799
800 return mp;
801}
802
803std::unique_ptr< QgsLineString > ogrGeometryToQgsLineString( OGRGeometryH geom )
804{
805 Qgis::WkbType wkbType = QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGR_G_GetGeometryType( geom ) );
806
807 int count = OGR_G_GetPointCount( geom );
808 QVector< double > x( count );
809 QVector< double > y( count );
810 QVector< double > z;
811 double *pz = nullptr;
812 if ( QgsWkbTypes::hasZ( wkbType ) )
813 {
814 z.resize( count );
815 pz = z.data();
816 }
817 double *pm = nullptr;
818 QVector< double > m;
819 if ( QgsWkbTypes::hasM( wkbType ) )
820 {
821 m.resize( count );
822 pm = m.data();
823 }
824 OGR_G_GetPointsZM( geom, x.data(), sizeof( double ), y.data(), sizeof( double ), pz, sizeof( double ), pm, sizeof( double ) );
825
826 return std::make_unique< QgsLineString>( x, y, z, m, wkbType == Qgis::WkbType::LineString25D );
827}
828
829std::unique_ptr< QgsMultiLineString > ogrGeometryToQgsMultiLineString( OGRGeometryH geom )
830{
831 std::unique_ptr< QgsMultiLineString > mp = std::make_unique< QgsMultiLineString >();
832
833 const int count = OGR_G_GetGeometryCount( geom );
834 mp->reserve( count );
835 for ( int i = 0; i < count; ++i )
836 {
837 mp->addGeometry( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, i ) ).release() );
838 }
839
840 return mp;
841}
842
843std::unique_ptr< QgsPolygon > ogrGeometryToQgsPolygon( OGRGeometryH geom )
844{
845 std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
846
847 const int count = OGR_G_GetGeometryCount( geom );
848 if ( count >= 1 )
849 {
850 polygon->setExteriorRing( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, 0 ) ).release() );
851 }
852
853 for ( int i = 1; i < count; ++i )
854 {
855 polygon->addInteriorRing( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, i ) ).release() );
856 }
857
858 return polygon;
859}
860
861std::unique_ptr< QgsMultiPolygon > ogrGeometryToQgsMultiPolygon( OGRGeometryH geom )
862{
863 std::unique_ptr< QgsMultiPolygon > polygon = std::make_unique< QgsMultiPolygon >();
864
865 const int count = OGR_G_GetGeometryCount( geom );
866 polygon->reserve( count );
867 for ( int i = 0; i < count; ++i )
868 {
869 polygon->addGeometry( ogrGeometryToQgsPolygon( OGR_G_GetGeometryRef( geom, i ) ).release() );
870 }
871
872 return polygon;
873}
874
876{
877 switch ( ogrGeomType )
878 {
879 case wkbUnknown: return Qgis::WkbType::Unknown;
880 case wkbPoint: return Qgis::WkbType::Point;
881 case wkbLineString: return Qgis::WkbType::LineString;
882 case wkbPolygon: return Qgis::WkbType::Polygon;
883 case wkbMultiPoint: return Qgis::WkbType::MultiPoint;
884 case wkbMultiLineString: return Qgis::WkbType::MultiLineString;
885 case wkbMultiPolygon: return Qgis::WkbType::MultiPolygon;
886 case wkbGeometryCollection: return Qgis::WkbType::GeometryCollection;
887 case wkbCircularString: return Qgis::WkbType::CircularString;
888 case wkbCompoundCurve: return Qgis::WkbType::CompoundCurve;
889 case wkbCurvePolygon: return Qgis::WkbType::CurvePolygon;
890 case wkbMultiCurve: return Qgis::WkbType::MultiCurve;
891 case wkbMultiSurface: return Qgis::WkbType::MultiSurface;
892 case wkbCurve: return Qgis::WkbType::Unknown; // not an actual concrete type
893 case wkbSurface: return Qgis::WkbType::Unknown; // not an actual concrete type
894 case wkbPolyhedralSurface: return Qgis::WkbType::Unknown; // no actual matching
895 case wkbTIN: return Qgis::WkbType::Unknown; // no actual matching
896 case wkbTriangle: return Qgis::WkbType::Triangle;
897
898 case wkbNone: return Qgis::WkbType::NoGeometry;
899 case wkbLinearRing: return Qgis::WkbType::LineString; // approximate match
900
901 case wkbCircularStringZ: return Qgis::WkbType::CircularStringZ;
902 case wkbCompoundCurveZ: return Qgis::WkbType::CompoundCurveZ;
903 case wkbCurvePolygonZ: return Qgis::WkbType::CurvePolygonZ;
904 case wkbMultiCurveZ: return Qgis::WkbType::MultiCurveZ;
905 case wkbMultiSurfaceZ: return Qgis::WkbType::MultiSurfaceZ;
906 case wkbCurveZ: return Qgis::WkbType::Unknown; // not an actual concrete type
907 case wkbSurfaceZ: return Qgis::WkbType::Unknown; // not an actual concrete type
908 case wkbPolyhedralSurfaceZ: return Qgis::WkbType::Unknown; // no actual matching
909 case wkbTINZ: return Qgis::WkbType::Unknown; // no actual matching
910 case wkbTriangleZ: return Qgis::WkbType::TriangleZ;
911
912 case wkbPointM: return Qgis::WkbType::PointM;
913 case wkbLineStringM: return Qgis::WkbType::LineStringM;
914 case wkbPolygonM: return Qgis::WkbType::PolygonM;
915 case wkbMultiPointM: return Qgis::WkbType::MultiPointM;
916 case wkbMultiLineStringM: return Qgis::WkbType::MultiLineStringM;
917 case wkbMultiPolygonM: return Qgis::WkbType::MultiPolygonM;
918 case wkbGeometryCollectionM: return Qgis::WkbType::GeometryCollectionM;
919 case wkbCircularStringM: return Qgis::WkbType::CircularStringM;
920 case wkbCompoundCurveM: return Qgis::WkbType::CompoundCurveM;
921 case wkbCurvePolygonM: return Qgis::WkbType::CurvePolygonM;
922 case wkbMultiCurveM: return Qgis::WkbType::MultiCurveM;
923 case wkbMultiSurfaceM: return Qgis::WkbType::MultiSurfaceM;
924 case wkbCurveM: return Qgis::WkbType::Unknown; // not an actual concrete type
925 case wkbSurfaceM: return Qgis::WkbType::Unknown; // not an actual concrete type
926 case wkbPolyhedralSurfaceM: return Qgis::WkbType::Unknown; // no actual matching
927 case wkbTINM: return Qgis::WkbType::Unknown; // no actual matching
928 case wkbTriangleM: return Qgis::WkbType::TriangleM;
929
930 case wkbPointZM: return Qgis::WkbType::PointZM;
931 case wkbLineStringZM: return Qgis::WkbType::LineStringZM;
932 case wkbPolygonZM: return Qgis::WkbType::PolygonZM;
933 case wkbMultiPointZM: return Qgis::WkbType::MultiPointZM;
934 case wkbMultiLineStringZM: return Qgis::WkbType::MultiLineStringZM;
935 case wkbMultiPolygonZM: return Qgis::WkbType::MultiPolygonZM;
936 case wkbGeometryCollectionZM: return Qgis::WkbType::GeometryCollectionZM;
937 case wkbCircularStringZM: return Qgis::WkbType::CircularStringZM;
938 case wkbCompoundCurveZM: return Qgis::WkbType::CompoundCurveZM;
939 case wkbCurvePolygonZM: return Qgis::WkbType::CurvePolygonZM;
940 case wkbMultiCurveZM: return Qgis::WkbType::MultiCurveZM;
941 case wkbMultiSurfaceZM: return Qgis::WkbType::MultiSurfaceZM;
942 case wkbCurveZM: return Qgis::WkbType::Unknown; // not an actual concrete type
943 case wkbSurfaceZM: return Qgis::WkbType::Unknown; // not an actual concrete type
944 case wkbPolyhedralSurfaceZM: return Qgis::WkbType::Unknown; // no actual matching
945 case wkbTINZM: return Qgis::WkbType::Unknown; // no actual matching
946 case wkbTriangleZM: return Qgis::WkbType::TriangleZM;
947
948 case wkbPoint25D: return Qgis::WkbType::PointZ;
949 case wkbLineString25D: return Qgis::WkbType::LineStringZ;
950 case wkbPolygon25D: return Qgis::WkbType::PolygonZ;
951 case wkbMultiPoint25D: return Qgis::WkbType::MultiPointZ;
952 case wkbMultiLineString25D: return Qgis::WkbType::MultiLineStringZ;
953 case wkbMultiPolygon25D: return Qgis::WkbType::MultiPolygonZ;
954 case wkbGeometryCollection25D: return Qgis::WkbType::GeometryCollectionZ;
955 }
956
957 // should not reach that point normally
959}
960
962{
963 if ( !geom )
964 return QgsGeometry();
965
966 const auto ogrGeomType = OGR_G_GetGeometryType( geom );
967 Qgis::WkbType wkbType = ogrGeometryTypeToQgsWkbType( ogrGeomType );
968
969 // optimised case for some geometry classes, avoiding wkb conversion on OGR/QGIS sides
970 // TODO - extend to other classes!
971 switch ( QgsWkbTypes::flatType( wkbType ) )
972 {
974 {
975 return QgsGeometry( ogrGeometryToQgsPoint( geom ) );
976 }
977
979 {
980 return QgsGeometry( ogrGeometryToQgsMultiPoint( geom ) );
981 }
982
984 {
985 return QgsGeometry( ogrGeometryToQgsLineString( geom ) );
986 }
987
989 {
991 }
992
994 {
995 return QgsGeometry( ogrGeometryToQgsPolygon( geom ) );
996 }
997
999 {
1000 return QgsGeometry( ogrGeometryToQgsMultiPolygon( geom ) );
1001 }
1002
1003 default:
1004 break;
1005 }
1006
1007 // Fallback to inefficient WKB conversions
1008
1009 if ( wkbFlatten( wkbType ) == wkbGeometryCollection )
1010 {
1011 // Shapefile MultiPatch can be reported as GeometryCollectionZ of TINZ
1012 if ( OGR_G_GetGeometryCount( geom ) >= 1 &&
1013 wkbFlatten( OGR_G_GetGeometryType( OGR_G_GetGeometryRef( geom, 0 ) ) ) == wkbTIN )
1014 {
1015 auto newGeom = OGR_G_ForceToMultiPolygon( OGR_G_Clone( geom ) );
1016 auto ret = ogrGeometryToQgsGeometry( newGeom );
1017 OGR_G_DestroyGeometry( newGeom );
1018 return ret;
1019 }
1020 }
1021
1022 // get the wkb representation
1023 int memorySize = OGR_G_WkbSize( geom );
1024 unsigned char *wkb = new unsigned char[memorySize];
1025 OGR_G_ExportToWkb( geom, static_cast<OGRwkbByteOrder>( QgsApplication::endian() ), wkb );
1026
1027 // Read original geometry type
1028 uint32_t origGeomType;
1029 memcpy( &origGeomType, wkb + 1, sizeof( uint32_t ) );
1030 bool hasZ = ( origGeomType >= 1000 && origGeomType < 2000 ) || ( origGeomType >= 3000 && origGeomType < 4000 );
1031 bool hasM = ( origGeomType >= 2000 && origGeomType < 3000 ) || ( origGeomType >= 3000 && origGeomType < 4000 );
1032
1033 // PolyhedralSurface and TINs are not supported, map them to multipolygons...
1034 if ( origGeomType % 1000 == 16 ) // is TIN, TINZ, TINM or TINZM
1035 {
1036 // TIN has the same wkb layout as a multipolygon, just need to overwrite the geom types...
1037 int nDims = 2 + hasZ + hasM;
1038 uint32_t newMultiType = static_cast<uint32_t>( QgsWkbTypes::zmType( Qgis::WkbType::MultiPolygon, hasZ, hasM ) );
1039 uint32_t newSingleType = static_cast<uint32_t>( QgsWkbTypes::zmType( Qgis::WkbType::Polygon, hasZ, hasM ) );
1040 unsigned char *wkbptr = wkb;
1041
1042 // Endianness
1043 wkbptr += 1;
1044
1045 // Overwrite geom type
1046 memcpy( wkbptr, &newMultiType, sizeof( uint32_t ) );
1047 wkbptr += 4;
1048
1049 // Geom count
1050 uint32_t numGeoms;
1051 memcpy( &numGeoms, wkb + 5, sizeof( uint32_t ) );
1052 wkbptr += 4;
1053
1054 // For each part, overwrite the geometry type to polygon (Z|M)
1055 for ( uint32_t i = 0; i < numGeoms; ++i )
1056 {
1057 // Endianness
1058 wkbptr += 1;
1059
1060 // Overwrite geom type
1061 memcpy( wkbptr, &newSingleType, sizeof( uint32_t ) );
1062 wkbptr += sizeof( uint32_t );
1063
1064 // skip coordinates
1065 uint32_t nRings;
1066 memcpy( &nRings, wkbptr, sizeof( uint32_t ) );
1067 wkbptr += sizeof( uint32_t );
1068
1069 for ( uint32_t j = 0; j < nRings; ++j )
1070 {
1071 uint32_t nPoints;
1072 memcpy( &nPoints, wkbptr, sizeof( uint32_t ) );
1073 wkbptr += sizeof( uint32_t ) + sizeof( double ) * nDims * nPoints;
1074 }
1075 }
1076 }
1077 else if ( origGeomType % 1000 == 15 ) // PolyhedralSurface, PolyhedralSurfaceZ, PolyhedralSurfaceM or PolyhedralSurfaceZM
1078 {
1079 // PolyhedralSurface has the same wkb layout as a MultiPolygon, just need to overwrite the geom type...
1080 uint32_t newType = static_cast<uint32_t>( QgsWkbTypes::zmType( Qgis::WkbType::MultiPolygon, hasZ, hasM ) );
1081 // Overwrite geom type
1082 memcpy( wkb + 1, &newType, sizeof( uint32_t ) );
1083 }
1084
1085 QgsGeometry g;
1086 g.fromWkb( wkb, memorySize );
1087 return g;
1088}
1089
1090QgsFeatureList QgsOgrUtils::stringToFeatureList( const QString &string, const QgsFields &fields, QTextCodec *encoding )
1091{
1092 QgsFeatureList features;
1093 if ( string.isEmpty() )
1094 return features;
1095
1096 QString randomFileName = QStringLiteral( "/vsimem/%1" ).arg( QUuid::createUuid().toString() );
1097
1098 // create memory file system object from string buffer
1099 QByteArray ba = string.toUtf8();
1100 VSIFCloseL( VSIFileFromMemBuffer( randomFileName.toUtf8().constData(), reinterpret_cast< GByte * >( ba.data() ),
1101 static_cast< vsi_l_offset >( ba.size() ), FALSE ) );
1102
1103 gdal::ogr_datasource_unique_ptr hDS( OGROpen( randomFileName.toUtf8().constData(), false, nullptr ) );
1104 if ( !hDS )
1105 {
1106 VSIUnlink( randomFileName.toUtf8().constData() );
1107 return features;
1108 }
1109
1110 OGRLayerH ogrLayer = OGR_DS_GetLayer( hDS.get(), 0 );
1111 if ( !ogrLayer )
1112 {
1113 hDS.reset();
1114 VSIUnlink( randomFileName.toUtf8().constData() );
1115 return features;
1116 }
1117
1119 while ( oFeat.reset( OGR_L_GetNextFeature( ogrLayer ) ), oFeat )
1120 {
1121 QgsFeature feat = readOgrFeature( oFeat.get(), fields, encoding );
1122 if ( feat.isValid() )
1123 features << feat;
1124 }
1125
1126 hDS.reset();
1127 VSIUnlink( randomFileName.toUtf8().constData() );
1128
1129 return features;
1130}
1131
1132QgsFields QgsOgrUtils::stringToFields( const QString &string, QTextCodec *encoding )
1133{
1134 QgsFields fields;
1135 if ( string.isEmpty() )
1136 return fields;
1137
1138 QString randomFileName = QStringLiteral( "/vsimem/%1" ).arg( QUuid::createUuid().toString() );
1139
1140 // create memory file system object from buffer
1141 QByteArray ba = string.toUtf8();
1142 VSIFCloseL( VSIFileFromMemBuffer( randomFileName.toUtf8().constData(), reinterpret_cast< GByte * >( ba.data() ),
1143 static_cast< vsi_l_offset >( ba.size() ), FALSE ) );
1144
1145 gdal::ogr_datasource_unique_ptr hDS( OGROpen( randomFileName.toUtf8().constData(), false, nullptr ) );
1146 if ( !hDS )
1147 {
1148 VSIUnlink( randomFileName.toUtf8().constData() );
1149 return fields;
1150 }
1151
1152 OGRLayerH ogrLayer = OGR_DS_GetLayer( hDS.get(), 0 );
1153 if ( !ogrLayer )
1154 {
1155 hDS.reset();
1156 VSIUnlink( randomFileName.toUtf8().constData() );
1157 return fields;
1158 }
1159
1161 //read in the first feature only
1162 if ( oFeat.reset( OGR_L_GetNextFeature( ogrLayer ) ), oFeat )
1163 {
1164 fields = readOgrFields( oFeat.get(), encoding );
1165 }
1166
1167 hDS.reset();
1168 VSIUnlink( randomFileName.toUtf8().constData() );
1169 return fields;
1170}
1171
1172QStringList QgsOgrUtils::cStringListToQStringList( char **stringList )
1173{
1174 if ( !stringList )
1175 return {};
1176
1177 QStringList strings;
1178 // presume null terminated string list
1179 for ( qgssize i = 0; stringList[i]; ++i )
1180 {
1181 strings.append( QString::fromUtf8( stringList[i] ) );
1182 }
1183
1184 return strings;
1185}
1186
1188{
1189 if ( !srs )
1190 return QString();
1191
1192 char *pszWkt = nullptr;
1193 const QByteArray multiLineOption = QStringLiteral( "MULTILINE=NO" ).toLocal8Bit();
1194 const QByteArray formatOption = QStringLiteral( "FORMAT=WKT2" ).toLocal8Bit();
1195 const char *const options[] = {multiLineOption.constData(), formatOption.constData(), nullptr};
1196 OSRExportToWktEx( srs, &pszWkt, options );
1197
1198 const QString res( pszWkt );
1199 CPLFree( pszWkt );
1200 return res;
1201}
1202
1204{
1205 const QString wkt = OGRSpatialReferenceToWkt( srs );
1206 if ( wkt.isEmpty() )
1208
1209 const char *authorityName = OSRGetAuthorityName( srs, nullptr );
1210 const char *authorityCode = OSRGetAuthorityCode( srs, nullptr );
1212 if ( authorityName && authorityCode )
1213 {
1214 QString authId = QString( authorityName ) + ':' + QString( authorityCode );
1215 OGRSpatialReferenceH ogrSrsTmp = OSRNewSpatialReference( nullptr );
1216 // Check that the CRS build from authId and the input one are the "same".
1217 if ( OSRSetFromUserInput( ogrSrsTmp, authId.toUtf8().constData() ) != OGRERR_NONE &&
1218 OSRIsSame( srs, ogrSrsTmp ) )
1219 {
1221 res.createFromUserInput( authId );
1222 }
1223 OSRDestroySpatialReference( ogrSrsTmp );
1224 }
1225 if ( !res.isValid() )
1227
1228#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,4,0)
1229 const double coordinateEpoch = OSRGetCoordinateEpoch( srs );
1230 if ( coordinateEpoch > 0 )
1231 res.setCoordinateEpoch( coordinateEpoch );
1232#endif
1233 return res;
1234}
1235
1237{
1238 if ( crs.isValid() )
1239 {
1240 OGRSpatialReferenceH ogrSrs = nullptr;
1241
1242 // First try instantiating the CRS from its authId. This will give a
1243 // more complete representation of the CRS for GDAL. In particular it might
1244 // help a few drivers to get the datum code, that would be missing in WKT-2.
1245 // See https://github.com/OSGeo/gdal/pull/5218
1246 const QString authId = crs.authid();
1247 const QString srsWkt = crs.toWkt( Qgis::CrsWktVariant::PreferredGdal );
1248 if ( !authId.isEmpty() )
1249 {
1250 ogrSrs = OSRNewSpatialReference( nullptr );
1251 if ( OSRSetFromUserInput( ogrSrs, authId.toUtf8().constData() ) == OGRERR_NONE )
1252 {
1253 // Check that the CRS build from WKT and authId are the "same".
1254 OGRSpatialReferenceH ogrSrsFromWkt = OSRNewSpatialReference( srsWkt.toUtf8().constData() );
1255 if ( ogrSrsFromWkt )
1256 {
1257 if ( !OSRIsSame( ogrSrs, ogrSrsFromWkt ) )
1258 {
1259 OSRDestroySpatialReference( ogrSrs );
1260 ogrSrs = ogrSrsFromWkt;
1261 }
1262 else
1263 {
1264 OSRDestroySpatialReference( ogrSrsFromWkt );
1265 }
1266 }
1267 }
1268 else
1269 {
1270 OSRDestroySpatialReference( ogrSrs );
1271 ogrSrs = nullptr;
1272 }
1273 }
1274 if ( !ogrSrs )
1275 {
1276 ogrSrs = OSRNewSpatialReference( srsWkt.toUtf8().constData() );
1277 }
1278 if ( ogrSrs )
1279 {
1280 OSRSetAxisMappingStrategy( ogrSrs, OAMS_TRADITIONAL_GIS_ORDER );
1281#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,4,0)
1282 if ( !std::isnan( crs.coordinateEpoch() ) )
1283 {
1284 OSRSetCoordinateEpoch( ogrSrs, crs.coordinateEpoch() );
1285 }
1286#endif
1287 return ogrSrs;
1288 }
1289 }
1290
1291 return nullptr;
1292}
1293
1294QString QgsOgrUtils::readShapefileEncoding( const QString &path )
1295{
1296 const QString cpgEncoding = readShapefileEncodingFromCpg( path );
1297 if ( !cpgEncoding.isEmpty() )
1298 return cpgEncoding;
1299
1300 return readShapefileEncodingFromLdid( path );
1301}
1302
1303QString QgsOgrUtils::readShapefileEncodingFromCpg( const QString &path )
1304{
1305 QString errCause;
1306 QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( path, false, QStringList(), 0, errCause, false );
1307 return layer ? layer->GetMetadataItem( QStringLiteral( "ENCODING_FROM_CPG" ), QStringLiteral( "SHAPEFILE" ) ) : QString();
1308}
1309
1311{
1312 QString errCause;
1313 QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( path, false, QStringList(), 0, errCause, false );
1314 return layer ? layer->GetMetadataItem( QStringLiteral( "ENCODING_FROM_LDID" ), QStringLiteral( "SHAPEFILE" ) ) : QString();
1315}
1316
1317QVariantMap QgsOgrUtils::parseStyleString( const QString &string )
1318{
1319 QVariantMap styles;
1320
1321 char **papszStyleString = CSLTokenizeString2( string.toUtf8().constData(), ";",
1322 CSLT_HONOURSTRINGS
1323 | CSLT_PRESERVEQUOTES
1324 | CSLT_PRESERVEESCAPES );
1325 for ( int i = 0; papszStyleString[i] != nullptr; ++i )
1326 {
1327 // style string format is:
1328 // <tool_name>([<tool_param>[,<tool_param>[,...]]])
1329
1330 // first extract tool name
1331 const thread_local QRegularExpression sToolPartRx( QStringLiteral( "^(.*?)\\((.*)\\)$" ) );
1332 const QString stylePart( papszStyleString[i] );
1333 const QRegularExpressionMatch match = sToolPartRx.match( stylePart );
1334 if ( !match.hasMatch() )
1335 continue;
1336
1337 const QString tool = match.captured( 1 );
1338 const QString params = match.captured( 2 );
1339
1340 char **papszTokens = CSLTokenizeString2( params.toUtf8().constData(), ",", CSLT_HONOURSTRINGS
1341 | CSLT_PRESERVEESCAPES );
1342
1343 QVariantMap toolParts;
1344 const thread_local QRegularExpression sToolParamRx( QStringLiteral( "^(.*?):(.*)$" ) );
1345 for ( int j = 0; papszTokens[j] != nullptr; ++j )
1346 {
1347 const QString toolPart( papszTokens[j] );
1348 const QRegularExpressionMatch toolMatch = sToolParamRx.match( toolPart );
1349 if ( !match.hasMatch() )
1350 continue;
1351
1352 // note we always convert the keys to lowercase, just to be safe...
1353 toolParts.insert( toolMatch.captured( 1 ).toLower(), toolMatch.captured( 2 ) );
1354 }
1355 CSLDestroy( papszTokens );
1356
1357 // note we always convert the keys to lowercase, just to be safe...
1358 styles.insert( tool.toLower(), toolParts );
1359 }
1360 CSLDestroy( papszStyleString );
1361 return styles;
1362}
1363
1364std::unique_ptr<QgsSymbol> QgsOgrUtils::symbolFromStyleString( const QString &string, Qgis::SymbolType type )
1365{
1366 const QVariantMap styles = parseStyleString( string );
1367
1368 auto convertSize = []( const QString & size, double & value, Qgis::RenderUnit & unit )->bool
1369 {
1370 const thread_local QRegularExpression sUnitRx = QRegularExpression( QStringLiteral( "^([\\d\\.]+)(g|px|pt|mm|cm|in)$" ) );
1371 const QRegularExpressionMatch match = sUnitRx.match( size );
1372 if ( match.hasMatch() )
1373 {
1374 value = match.captured( 1 ).toDouble();
1375 const QString unitString = match.captured( 2 );
1376 if ( unitString.compare( QLatin1String( "px" ), Qt::CaseInsensitive ) == 0 )
1377 {
1378 // pixels are a poor unit choice for QGIS -- they render badly in hidpi layouts. Convert to points instead, using
1379 // a 96 dpi conversion
1380 static constexpr double PT_TO_INCHES_FACTOR = 1 / 72.0;
1381 static constexpr double PX_TO_PT_FACTOR = 1 / ( 96.0 * PT_TO_INCHES_FACTOR );
1383 value *= PX_TO_PT_FACTOR;
1384 return true;
1385 }
1386 else if ( unitString.compare( QLatin1String( "pt" ), Qt::CaseInsensitive ) == 0 )
1387 {
1389 return true;
1390 }
1391 else if ( unitString.compare( QLatin1String( "mm" ), Qt::CaseInsensitive ) == 0 )
1392 {
1394 return true;
1395 }
1396 else if ( unitString.compare( QLatin1String( "cm" ), Qt::CaseInsensitive ) == 0 )
1397 {
1398 value *= 10;
1400 return true;
1401 }
1402 else if ( unitString.compare( QLatin1String( "in" ), Qt::CaseInsensitive ) == 0 )
1403 {
1405 return true;
1406 }
1407 else if ( unitString.compare( QLatin1String( "g" ), Qt::CaseInsensitive ) == 0 )
1408 {
1410 return true;
1411 }
1412 QgsDebugError( QStringLiteral( "Unknown unit %1" ).arg( unitString ) );
1413 }
1414 else
1415 {
1416 QgsDebugError( QStringLiteral( "Could not parse style size %1" ).arg( size ) );
1417 }
1418 return false;
1419 };
1420
1421 auto convertColor = []( const QString & string ) -> QColor
1422 {
1423 if ( string.isEmpty() )
1424 return QColor();
1425
1426 const thread_local QRegularExpression sColorWithAlphaRx = QRegularExpression( QStringLiteral( "^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})$" ) );
1427 const QRegularExpressionMatch match = sColorWithAlphaRx.match( string );
1428 if ( match.hasMatch() )
1429 {
1430 // need to convert #RRGGBBAA to #AARRGGBB for QColor
1431 return QColor( QStringLiteral( "#%1%2" ).arg( match.captured( 2 ), match.captured( 1 ) ) );
1432 }
1433 else
1434 {
1435 return QColor( string );
1436 }
1437 };
1438
1439 auto convertPen = [&convertColor, &convertSize, string]( const QVariantMap & lineStyle ) -> std::unique_ptr< QgsSymbol >
1440 {
1441 QColor color = convertColor( lineStyle.value( QStringLiteral( "c" ), QStringLiteral( "#000000" ) ).toString() );
1442
1443 double lineWidth = DEFAULT_SIMPLELINE_WIDTH;
1445 convertSize( lineStyle.value( QStringLiteral( "w" ) ).toString(), lineWidth, lineWidthUnit );
1446
1447 // if the pen is a mapinfo pen, use dedicated converter for more accurate results
1448 const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-pen-(\\d+)" ) );
1449 const QRegularExpressionMatch match = sMapInfoId.match( string );
1450 if ( match.hasMatch() )
1451 {
1452 const int penId = match.captured( 1 ).toInt();
1454 std::unique_ptr<QgsSymbol> res( QgsMapInfoSymbolConverter::convertLineSymbol( penId, context, color, lineWidth, lineWidthUnit ) );
1455 if ( res )
1456 return res;
1457 }
1458
1459 std::unique_ptr< QgsSimpleLineSymbolLayer > simpleLine = std::make_unique< QgsSimpleLineSymbolLayer >( color, lineWidth );
1460 simpleLine->setWidthUnit( lineWidthUnit );
1461
1462 // pattern
1463 const QString pattern = lineStyle.value( QStringLiteral( "p" ) ).toString();
1464 if ( !pattern.isEmpty() )
1465 {
1466 const thread_local QRegularExpression sPatternUnitRx = QRegularExpression( QStringLiteral( "^([\\d\\.\\s]+)(g|px|pt|mm|cm|in)$" ) );
1467 const QRegularExpressionMatch match = sPatternUnitRx.match( pattern );
1468 if ( match.hasMatch() )
1469 {
1470 const QStringList patternValues = match.captured( 1 ).split( ' ' );
1471 QVector< qreal > dashPattern;
1473 for ( const QString &val : patternValues )
1474 {
1475 double length;
1476 convertSize( val + match.captured( 2 ), length, patternUnits );
1477 dashPattern.push_back( length * lineWidth * 2 );
1478 }
1479
1480 simpleLine->setCustomDashVector( dashPattern );
1481 simpleLine->setCustomDashPatternUnit( patternUnits );
1482 simpleLine->setUseCustomDashPattern( true );
1483 }
1484 }
1485
1486 Qt::PenCapStyle capStyle = Qt::FlatCap;
1487 Qt::PenJoinStyle joinStyle = Qt::MiterJoin;
1488 // workaround https://github.com/OSGeo/gdal/pull/3509 in older GDAL versions
1489 const QString id = lineStyle.value( QStringLiteral( "id" ) ).toString();
1490 if ( id.contains( QLatin1String( "mapinfo-pen" ), Qt::CaseInsensitive ) )
1491 {
1492 // MapInfo renders all lines using a round pen cap and round pen join
1493 // which are not the default values for OGR pen cap/join styles. So we need to explicitly
1494 // override the OGR default values here on older GDAL versions
1495 capStyle = Qt::RoundCap;
1496 joinStyle = Qt::RoundJoin;
1497 }
1498
1499 // pen cap
1500 const QString penCap = lineStyle.value( QStringLiteral( "cap" ) ).toString();
1501 if ( penCap.compare( QLatin1String( "b" ), Qt::CaseInsensitive ) == 0 )
1502 {
1503 capStyle = Qt::FlatCap;
1504 }
1505 else if ( penCap.compare( QLatin1String( "r" ), Qt::CaseInsensitive ) == 0 )
1506 {
1507 capStyle = Qt::RoundCap;
1508 }
1509 else if ( penCap.compare( QLatin1String( "p" ), Qt::CaseInsensitive ) == 0 )
1510 {
1511 capStyle = Qt::SquareCap;
1512 }
1513 simpleLine->setPenCapStyle( capStyle );
1514
1515 // pen join
1516 const QString penJoin = lineStyle.value( QStringLiteral( "j" ) ).toString();
1517 if ( penJoin.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0 )
1518 {
1519 joinStyle = Qt::MiterJoin;
1520 }
1521 else if ( penJoin.compare( QLatin1String( "r" ), Qt::CaseInsensitive ) == 0 )
1522 {
1523 joinStyle = Qt::RoundJoin;
1524 }
1525 else if ( penJoin.compare( QLatin1String( "b" ), Qt::CaseInsensitive ) == 0 )
1526 {
1527 joinStyle = Qt::BevelJoin;
1528 }
1529 simpleLine->setPenJoinStyle( joinStyle );
1530
1531 const QString priority = lineStyle.value( QStringLiteral( "l" ) ).toString();
1532 if ( !priority.isEmpty() )
1533 {
1534 simpleLine->setRenderingPass( priority.toInt() );
1535 }
1536 return std::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << simpleLine.release() );
1537 };
1538
1539 auto convertBrush = [&convertColor]( const QVariantMap & brushStyle ) -> std::unique_ptr< QgsSymbol >
1540 {
1541 const QColor foreColor = convertColor( brushStyle.value( QStringLiteral( "fc" ), QStringLiteral( "#000000" ) ).toString() );
1542 const QColor backColor = convertColor( brushStyle.value( QStringLiteral( "bc" ), QString() ).toString() );
1543
1544 const QString id = brushStyle.value( QStringLiteral( "id" ) ).toString();
1545
1546 // if the pen is a mapinfo brush, use dedicated converter for more accurate results
1547 const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-brush-(\\d+)" ) );
1548 const QRegularExpressionMatch match = sMapInfoId.match( id );
1549 if ( match.hasMatch() )
1550 {
1551 const int brushId = match.captured( 1 ).toInt();
1553 std::unique_ptr<QgsSymbol> res( QgsMapInfoSymbolConverter::convertFillSymbol( brushId, context, foreColor, backColor ) );
1554 if ( res )
1555 return res;
1556 }
1557
1558 const thread_local QRegularExpression sOgrId = QRegularExpression( QStringLiteral( "ogr-brush-(\\d+)" ) );
1559 const QRegularExpressionMatch ogrMatch = sOgrId.match( id );
1560
1561 Qt::BrushStyle style = Qt::SolidPattern;
1562 if ( ogrMatch.hasMatch() )
1563 {
1564 const int brushId = ogrMatch.captured( 1 ).toInt();
1565 switch ( brushId )
1566 {
1567 case 0:
1568 style = Qt::SolidPattern;
1569 break;
1570
1571 case 1:
1572 style = Qt::NoBrush;
1573 break;
1574
1575 case 2:
1576 style = Qt::HorPattern;
1577 break;
1578
1579 case 3:
1580 style = Qt::VerPattern;
1581 break;
1582
1583 case 4:
1584 style = Qt::FDiagPattern;
1585 break;
1586
1587 case 5:
1588 style = Qt::BDiagPattern;
1589 break;
1590
1591 case 6:
1592 style = Qt::CrossPattern;
1593 break;
1594
1595 case 7:
1596 style = Qt::DiagCrossPattern;
1597 break;
1598 }
1599 }
1600
1601 QgsSymbolLayerList layers;
1602 if ( backColor.isValid() && style != Qt::SolidPattern && style != Qt::NoBrush )
1603 {
1604 std::unique_ptr< QgsSimpleFillSymbolLayer > backgroundFill = std::make_unique< QgsSimpleFillSymbolLayer >( backColor );
1605 backgroundFill->setLocked( true );
1606 backgroundFill->setStrokeStyle( Qt::NoPen );
1607 layers << backgroundFill.release();
1608 }
1609
1610 std::unique_ptr< QgsSimpleFillSymbolLayer > foregroundFill = std::make_unique< QgsSimpleFillSymbolLayer >( foreColor );
1611 foregroundFill->setBrushStyle( style );
1612 foregroundFill->setStrokeStyle( Qt::NoPen );
1613
1614 const QString priority = brushStyle.value( QStringLiteral( "l" ) ).toString();
1615 if ( !priority.isEmpty() )
1616 {
1617 foregroundFill->setRenderingPass( priority.toInt() );
1618 }
1619 layers << foregroundFill.release();
1620 return std::make_unique< QgsFillSymbol >( layers );
1621 };
1622
1623 auto convertSymbol = [&convertColor, &convertSize, string]( const QVariantMap & symbolStyle ) -> std::unique_ptr< QgsSymbol >
1624 {
1625 const QColor color = convertColor( symbolStyle.value( QStringLiteral( "c" ), QStringLiteral( "#000000" ) ).toString() );
1626
1627 double symbolSize = DEFAULT_SIMPLEMARKER_SIZE;
1629 convertSize( symbolStyle.value( QStringLiteral( "s" ) ).toString(), symbolSize, symbolSizeUnit );
1630
1631 const double angle = symbolStyle.value( QStringLiteral( "a" ), QStringLiteral( "0" ) ).toDouble();
1632
1633 const QString id = symbolStyle.value( QStringLiteral( "id" ) ).toString();
1634
1635 // if the symbol is a mapinfo symbol, use dedicated converter for more accurate results
1636 const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-sym-(\\d+)" ) );
1637 const QRegularExpressionMatch match = sMapInfoId.match( id );
1638 if ( match.hasMatch() )
1639 {
1640 const int symbolId = match.captured( 1 ).toInt();
1642
1643 // ogr interpretations of mapinfo symbol sizes are too large -- scale these down
1644 symbolSize *= 0.61;
1645
1646 std::unique_ptr<QgsSymbol> res( QgsMapInfoSymbolConverter::convertMarkerSymbol( symbolId, context, color, symbolSize, symbolSizeUnit ) );
1647 if ( res )
1648 return res;
1649 }
1650
1651 std::unique_ptr< QgsMarkerSymbolLayer > markerLayer;
1652
1653 const thread_local QRegularExpression sFontId = QRegularExpression( QStringLiteral( "font-sym-(\\d+)" ) );
1654 const QRegularExpressionMatch fontMatch = sFontId.match( id );
1655 if ( fontMatch.hasMatch() )
1656 {
1657 const int symId = fontMatch.captured( 1 ).toInt();
1658 const QStringList families = symbolStyle.value( QStringLiteral( "f" ), QString() ).toString().split( ',' );
1659
1660 bool familyFound = false;
1661 QString fontFamily;
1662 QString matched;
1663 for ( const QString &family : std::as_const( families ) )
1664 {
1665 const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( family );
1666
1667 if ( QgsFontUtils::fontFamilyMatchOnSystem( processedFamily ) ||
1668 QgsApplication::fontManager()->tryToDownloadFontFamily( processedFamily, matched ) )
1669 {
1670 familyFound = true;
1671 fontFamily = processedFamily;
1672 break;
1673 }
1674 }
1675
1676 if ( familyFound )
1677 {
1678 std::unique_ptr< QgsFontMarkerSymbolLayer > fontMarker = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, QChar( symId ), symbolSize );
1679 fontMarker->setSizeUnit( symbolSizeUnit );
1680 fontMarker->setAngle( -angle );
1681
1682 fontMarker->setColor( color );
1683
1684 const QColor strokeColor = convertColor( symbolStyle.value( QStringLiteral( "o" ), QString() ).toString() );
1685 if ( strokeColor.isValid() )
1686 {
1687 fontMarker->setStrokeColor( strokeColor );
1688 fontMarker->setStrokeWidth( 1 );
1689 fontMarker->setStrokeWidthUnit( Qgis::RenderUnit::Points );
1690 }
1691 else
1692 {
1693 fontMarker->setStrokeWidth( 0 );
1694 }
1695
1696 markerLayer = std::move( fontMarker );
1697 }
1698 else if ( !families.empty() )
1699 {
1700 // couldn't even find a matching font in the backup list
1701 QgsMessageLog::logMessage( QObject::tr( "Font %1 not found on system" ).arg( families.at( 0 ) ) );
1702 }
1703 }
1704
1705 if ( !markerLayer )
1706 {
1707 const thread_local QRegularExpression sOgrId = QRegularExpression( QStringLiteral( "ogr-sym-(\\d+)" ) );
1708 const QRegularExpressionMatch ogrMatch = sOgrId.match( id );
1709
1710 Qgis::MarkerShape shape;
1711 bool isFilled = true;
1712 if ( ogrMatch.hasMatch() )
1713 {
1714 const int symId = ogrMatch.captured( 1 ).toInt();
1715 switch ( symId )
1716 {
1717 case 0:
1719 break;
1720
1721 case 1:
1723 break;
1724
1725 case 2:
1726 isFilled = false;
1728 break;
1729
1730 case 3:
1732 break;
1733
1734 case 4:
1735 isFilled = false;
1737 break;
1738
1739 case 5:
1741 break;
1742
1743 case 6:
1744 isFilled = false;
1746 break;
1747
1748 case 7:
1750 break;
1751
1752 case 8:
1753 isFilled = false;
1755 break;
1756
1757 case 9:
1759 break;
1760
1761 case 10:
1763 break;
1764
1765 default:
1766 isFilled = false;
1767 shape = Qgis::MarkerShape::Square; // to initialize the variable
1768 break;
1769 }
1770 }
1771 else
1772 {
1773 isFilled = false;
1774 shape = Qgis::MarkerShape::Square; // to initialize the variable
1775 }
1776
1777 std::unique_ptr< QgsSimpleMarkerSymbolLayer > simpleMarker = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, symbolSize, -angle );
1778 simpleMarker->setSizeUnit( symbolSizeUnit );
1779 simpleMarker->setStrokeWidth( 1.0 );
1780 simpleMarker->setStrokeWidthUnit( Qgis::RenderUnit::Points );
1781
1782 if ( isFilled && QgsSimpleMarkerSymbolLayer::shapeIsFilled( shape ) )
1783 {
1784 simpleMarker->setColor( color );
1785 simpleMarker->setStrokeStyle( Qt::NoPen );
1786 }
1787 else
1788 {
1789 simpleMarker->setFillColor( QColor( 0, 0, 0, 0 ) );
1790 simpleMarker->setStrokeColor( color );
1791 }
1792
1793 const QColor strokeColor = convertColor( symbolStyle.value( QStringLiteral( "o" ), QString() ).toString() );
1794 if ( strokeColor.isValid() )
1795 {
1796 simpleMarker->setStrokeColor( strokeColor );
1797 simpleMarker->setStrokeStyle( Qt::SolidLine );
1798 }
1799
1800 markerLayer = std::move( simpleMarker );
1801 }
1802
1803 return std::make_unique< QgsMarkerSymbol >( QgsSymbolLayerList() << markerLayer.release() );
1804 };
1805
1806 switch ( type )
1807 {
1809 if ( styles.contains( QStringLiteral( "symbol" ) ) )
1810 {
1811 const QVariantMap symbolStyle = styles.value( QStringLiteral( "symbol" ) ).toMap();
1812 return convertSymbol( symbolStyle );
1813 }
1814 else
1815 {
1816 return nullptr;
1817 }
1818
1820 if ( styles.contains( QStringLiteral( "pen" ) ) )
1821 {
1822 // line symbol type
1823 const QVariantMap lineStyle = styles.value( QStringLiteral( "pen" ) ).toMap();
1824 return convertPen( lineStyle );
1825 }
1826 else
1827 {
1828 return nullptr;
1829 }
1830
1832 {
1833 std::unique_ptr< QgsSymbol > fillSymbol = std::make_unique< QgsFillSymbol >();
1834 if ( styles.contains( QStringLiteral( "brush" ) ) )
1835 {
1836 const QVariantMap brushStyle = styles.value( QStringLiteral( "brush" ) ).toMap();
1837 fillSymbol = convertBrush( brushStyle );
1838 }
1839 else
1840 {
1841 std::unique_ptr< QgsSimpleFillSymbolLayer > emptyFill = std::make_unique< QgsSimpleFillSymbolLayer >();
1842 emptyFill->setBrushStyle( Qt::NoBrush );
1843 fillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << emptyFill.release() );
1844 }
1845
1846 std::unique_ptr< QgsSymbol > penSymbol;
1847 if ( styles.contains( QStringLiteral( "pen" ) ) )
1848 {
1849 const QVariantMap lineStyle = styles.value( QStringLiteral( "pen" ) ).toMap();
1850 penSymbol = convertPen( lineStyle );
1851 }
1852
1853 if ( penSymbol )
1854 {
1855 const int count = penSymbol->symbolLayerCount();
1856
1857 if ( count == 1 )
1858 {
1859 // if only one pen symbol layer, let's try and combine it with the topmost brush layer, so that the resultant QGIS symbol is simpler
1860 if ( QgsSymbolLayerUtils::condenseFillAndOutline( dynamic_cast< QgsFillSymbolLayer * >( fillSymbol->symbolLayer( fillSymbol->symbolLayerCount() - 1 ) ),
1861 dynamic_cast< QgsLineSymbolLayer * >( penSymbol->symbolLayer( 0 ) ) ) )
1862 return fillSymbol;
1863 }
1864
1865 for ( int i = 0; i < count; ++i )
1866 {
1867 std::unique_ptr< QgsSymbolLayer > layer( penSymbol->takeSymbolLayer( 0 ) );
1868 layer->setLocked( true );
1869 fillSymbol->appendSymbolLayer( layer.release() );
1870 }
1871 }
1872
1873 return fillSymbol;
1874 }
1875
1877 break;
1878 }
1879
1880 return nullptr;
1881}
1882
1883void QgsOgrUtils::ogrFieldTypeToQVariantType( OGRFieldType ogrType, OGRFieldSubType ogrSubType, QMetaType::Type &variantType, QMetaType::Type &variantSubType )
1884{
1885 variantType = QMetaType::Type::UnknownType;
1886 variantSubType = QMetaType::Type::UnknownType;
1887
1888 switch ( ogrType )
1889 {
1890 case OFTInteger:
1891 if ( ogrSubType == OFSTBoolean )
1892 {
1893 variantType = QMetaType::Type::Bool;
1894 ogrSubType = OFSTBoolean;
1895 }
1896 else
1897 variantType = QMetaType::Type::Int;
1898 break;
1899 case OFTInteger64:
1900 variantType = QMetaType::Type::LongLong;
1901 break;
1902 case OFTReal:
1903 variantType = QMetaType::Type::Double;
1904 break;
1905 case OFTDate:
1906 variantType = QMetaType::Type::QDate;
1907 break;
1908 case OFTTime:
1909 variantType = QMetaType::Type::QTime;
1910 break;
1911 case OFTDateTime:
1912 variantType = QMetaType::Type::QDateTime;
1913 break;
1914
1915 case OFTBinary:
1916 variantType = QMetaType::Type::QByteArray;
1917 break;
1918
1919 case OFTString:
1920 case OFTWideString:
1921 if ( ogrSubType == OFSTJSON )
1922 {
1923 ogrSubType = OFSTJSON;
1924 variantType = QMetaType::Type::QVariantMap;
1925 variantSubType = QMetaType::Type::QString;
1926 }
1927 else
1928 {
1929 variantType = QMetaType::Type::QString;
1930 }
1931 break;
1932
1933 case OFTStringList:
1934 case OFTWideStringList:
1935 variantType = QMetaType::Type::QStringList;
1936 variantSubType = QMetaType::Type::QString;
1937 break;
1938
1939 case OFTIntegerList:
1940 variantType = QMetaType::Type::QVariantList;
1941 variantSubType = QMetaType::Type::Int;
1942 break;
1943
1944 case OFTRealList:
1945 variantType = QMetaType::Type::QVariantList;
1946 variantSubType = QMetaType::Type::Double;
1947 break;
1948
1949 case OFTInteger64List:
1950 variantType = QMetaType::Type::QVariantList;
1951 variantSubType = QMetaType::Type::LongLong;
1952 break;
1953 }
1954}
1955
1956void QgsOgrUtils::variantTypeToOgrFieldType( QMetaType::Type variantType, OGRFieldType &ogrType, OGRFieldSubType &ogrSubType )
1957{
1958 ogrSubType = OFSTNone;
1959 switch ( variantType )
1960 {
1961 case QMetaType::Type::Bool:
1962 ogrType = OFTInteger;
1963 ogrSubType = OFSTBoolean;
1964 break;
1965
1966 case QMetaType::Type::Int:
1967 ogrType = OFTInteger;
1968 break;
1969
1970 case QMetaType::Type::LongLong:
1971 ogrType = OFTInteger64;
1972 break;
1973
1974 case QMetaType::Type::Double:
1975 ogrType = OFTReal;
1976 break;
1977
1978 case QMetaType::Type::QChar:
1979 case QMetaType::Type::QString:
1980 ogrType = OFTString;
1981 break;
1982
1983 case QMetaType::Type::QStringList:
1984 ogrType = OFTStringList;
1985 break;
1986
1987 case QMetaType::Type::QByteArray:
1988 ogrType = OFTBinary;
1989 break;
1990
1991 case QMetaType::Type::QDate:
1992 ogrType = OFTDate;
1993 break;
1994
1995 case QMetaType::Type::QTime:
1996 ogrType = OFTTime;
1997 break;
1998 case QMetaType::Type::QDateTime:
1999 ogrType = OFTDateTime;
2000 break;
2001
2002 default:
2003 ogrType = OFTString;
2004 break;
2005 }
2006}
2007
2008QVariant QgsOgrUtils::stringToVariant( OGRFieldType type, OGRFieldSubType, const QString &string )
2009{
2010 if ( string.isEmpty() )
2011 return QVariant();
2012
2013 bool ok = false;
2014 QVariant res;
2015 switch ( type )
2016 {
2017 case OFTInteger:
2018 res = string.toInt( &ok );
2019 break;
2020
2021 case OFTInteger64:
2022 res = string.toLongLong( &ok );
2023 break;
2024
2025 case OFTReal:
2026 res = string.toDouble( &ok );
2027 break;
2028
2029 case OFTString:
2030 case OFTWideString:
2031 res = string;
2032 ok = true;
2033 break;
2034
2035 case OFTDate:
2036 res = QDate::fromString( string, Qt::ISODate );
2037 ok = res.isValid();
2038 break;
2039
2040 case OFTTime:
2041 res = QTime::fromString( string, Qt::ISODate );
2042 ok = res.isValid();
2043 break;
2044
2045 case OFTDateTime:
2046 res = QDateTime::fromString( string, Qt::ISODate );
2047 ok = res.isValid();
2048 break;
2049
2050 default:
2051 res = string;
2052 ok = true;
2053 break;
2054 }
2055
2056 return ok ? res : QVariant();
2057}
2058
2059QList<QgsVectorDataProvider::NativeType> QgsOgrUtils::nativeFieldTypesForDriver( GDALDriverH driver )
2060{
2061 if ( !driver )
2062 return {};
2063
2064 const QString driverName = QString::fromUtf8( GDALGetDriverShortName( driver ) );
2065
2066 int nMaxIntLen = 11;
2067 int nMaxInt64Len = 21;
2068 int nMaxDoubleLen = 20;
2069 int nMaxDoublePrec = 15;
2070 int nDateLen = 8;
2071 if ( driverName == QLatin1String( "GPKG" ) )
2072 {
2073 // GPKG only supports field length for text (and binary)
2074 nMaxIntLen = 0;
2075 nMaxInt64Len = 0;
2076 nMaxDoubleLen = 0;
2077 nMaxDoublePrec = 0;
2078 nDateLen = 0;
2079 }
2080
2081 QList<QgsVectorDataProvider::NativeType> nativeTypes;
2082 nativeTypes
2083 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::Int ), QStringLiteral( "integer" ), QMetaType::Type::Int, 0, nMaxIntLen )
2084 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::LongLong ), QStringLiteral( "integer64" ), QMetaType::Type::LongLong, 0, nMaxInt64Len )
2085 << QgsVectorDataProvider::NativeType( QObject::tr( "Decimal number (real)" ), QStringLiteral( "double" ), QMetaType::Type::Double, 0, nMaxDoubleLen, 0, nMaxDoublePrec )
2086 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QString ), QStringLiteral( "string" ), QMetaType::Type::QString, 0, 65535 );
2087
2088 if ( driverName == QLatin1String( "GPKG" ) )
2089 nativeTypes << QgsVectorDataProvider::NativeType( QObject::tr( "JSON (string)" ), QStringLiteral( "JSON" ), QMetaType::Type::QVariantMap, 0, 0, 0, 0, QMetaType::Type::QString );
2090
2091 bool supportsDate = true;
2092 bool supportsTime = true;
2093 bool supportsDateTime = true;
2094 bool supportsBinary = false;
2095 bool supportIntegerList = false;
2096 bool supportInteger64List = false;
2097 bool supportRealList = false;
2098 bool supportsStringList = false;
2099
2100 // For drivers that advertise their data type, use that instead of the
2101 // above hardcoded defaults.
2102 if ( const char *pszDataTypes = GDALGetMetadataItem( driver, GDAL_DMD_CREATIONFIELDDATATYPES, nullptr ) )
2103 {
2104 char **papszTokens = CSLTokenizeString2( pszDataTypes, " ", 0 );
2105 supportsDate = CSLFindString( papszTokens, "Date" ) >= 0;
2106 supportsTime = CSLFindString( papszTokens, "Time" ) >= 0;
2107 supportsDateTime = CSLFindString( papszTokens, "DateTime" ) >= 0;
2108 supportsBinary = CSLFindString( papszTokens, "Binary" ) >= 0;
2109 supportIntegerList = CSLFindString( papszTokens, "IntegerList" ) >= 0;
2110 supportInteger64List = CSLFindString( papszTokens, "Integer64List" ) >= 0;
2111 supportRealList = CSLFindString( papszTokens, "RealList" ) >= 0;
2112 supportsStringList = CSLFindString( papszTokens, "StringList" ) >= 0;
2113 CSLDestroy( papszTokens );
2114 }
2115
2116 // Older versions of GDAL incorrectly report that shapefiles support
2117 // DateTime.
2118#if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,2,0)
2119 if ( driverName == QLatin1String( "ESRI Shapefile" ) )
2120 {
2121 supportsDateTime = false;
2122 }
2123#endif
2124
2125 if ( supportsDate )
2126 {
2127 nativeTypes
2128 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QDate ), QStringLiteral( "date" ), QMetaType::Type::QDate, nDateLen, nDateLen );
2129 }
2130 if ( supportsTime )
2131 {
2132 nativeTypes
2133 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QTime ), QStringLiteral( "time" ), QMetaType::Type::QTime );
2134 }
2135 if ( supportsDateTime )
2136 {
2137 nativeTypes
2138 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QDateTime ), QStringLiteral( "datetime" ), QMetaType::Type::QDateTime );
2139 }
2140 if ( supportsBinary )
2141 {
2142 nativeTypes
2143 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QByteArray ), QStringLiteral( "binary" ), QMetaType::Type::QByteArray );
2144 }
2145 if ( supportIntegerList )
2146 {
2147 nativeTypes
2148 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QVariantList, QMetaType::Type::Int ), QStringLiteral( "integerlist" ), QMetaType::Type::QVariantList, 0, 0, 0, 0, QMetaType::Type::Int );
2149 }
2150 if ( supportInteger64List )
2151 {
2152 nativeTypes
2153 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QVariantList, QMetaType::Type::LongLong ), QStringLiteral( "integer64list" ), QMetaType::Type::QVariantList, 0, 0, 0, 0, QMetaType::Type::LongLong );
2154 }
2155 if ( supportRealList )
2156 {
2157 nativeTypes
2158 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QVariantList, QMetaType::Type::Double ), QStringLiteral( "doublelist" ), QMetaType::Type::QVariantList, 0, 0, 0, 0, QMetaType::Type::Double );
2159 }
2160 if ( supportsStringList )
2161 {
2162 nativeTypes
2163 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QStringList ), QStringLiteral( "stringlist" ), QMetaType::Type::QVariantList, 0, 0, 0, 0, QMetaType::Type::QString );
2164 }
2165
2166 const char *pszDataSubTypes = GDALGetMetadataItem( driver, GDAL_DMD_CREATIONFIELDDATASUBTYPES, nullptr );
2167 if ( pszDataSubTypes && strstr( pszDataSubTypes, "Boolean" ) )
2168 {
2169 // boolean data type
2170 nativeTypes
2171 << QgsVectorDataProvider::NativeType( QObject::tr( "Boolean" ), QStringLiteral( "bool" ), QMetaType::Type::Bool );
2172 }
2173
2174 return nativeTypes;
2175}
2176
2177
2178#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,3,0)
2179std::unique_ptr< QgsFieldDomain > QgsOgrUtils::convertFieldDomain( OGRFieldDomainH domain )
2180{
2181 if ( !domain )
2182 return nullptr;
2183
2184 const QString name{ OGR_FldDomain_GetName( domain ) };
2185 const QString description{ OGR_FldDomain_GetDescription( domain ) };
2186
2187 QMetaType::Type fieldType = QMetaType::Type::UnknownType;
2188 QMetaType::Type fieldSubType = QMetaType::Type::UnknownType;
2189 const OGRFieldType domainFieldType = OGR_FldDomain_GetFieldType( domain );
2190 const OGRFieldSubType domainFieldSubType = OGR_FldDomain_GetFieldSubType( domain );
2191 ogrFieldTypeToQVariantType( domainFieldType, domainFieldSubType, fieldType, fieldSubType );
2192
2193 std::unique_ptr< QgsFieldDomain > res;
2194 switch ( OGR_FldDomain_GetDomainType( domain ) )
2195 {
2196 case OFDT_CODED:
2197 {
2198 QList< QgsCodedValue > values;
2199 const OGRCodedValue *codedValue = OGR_CodedFldDomain_GetEnumeration( domain );
2200 while ( codedValue && codedValue->pszCode )
2201 {
2202 const QString code( codedValue->pszCode );
2203
2204 // if pszValue is null then it indicates we are working with a set of acceptable values which aren't
2205 // coded. In this case we copy the code as the value so that QGIS exposes the domain as a choice of
2206 // the valid code values.
2207 const QString value( codedValue->pszValue ? codedValue->pszValue : codedValue->pszCode );
2208 values.append( QgsCodedValue( stringToVariant( domainFieldType, domainFieldSubType, code ), value ) );
2209
2210 codedValue++;
2211 }
2212
2213 res = std::make_unique< QgsCodedFieldDomain >( name, description, fieldType, values );
2214 break;
2215 }
2216
2217 case OFDT_RANGE:
2218 {
2219 QVariant minValue;
2220 bool minIsInclusive = false;
2221 if ( const OGRField *min = OGR_RangeFldDomain_GetMin( domain, &minIsInclusive ) )
2222 {
2223 minValue = QgsOgrUtils::OGRFieldtoVariant( min, domainFieldType );
2224 }
2225 QVariant maxValue;
2226 bool maxIsInclusive = false;
2227 if ( const OGRField *max = OGR_RangeFldDomain_GetMax( domain, &maxIsInclusive ) )
2228 {
2229 maxValue = QgsOgrUtils::OGRFieldtoVariant( max, domainFieldType );
2230 }
2231
2232 res = std::make_unique< QgsRangeFieldDomain >( name, description, fieldType,
2233 minValue, minIsInclusive,
2234 maxValue, maxIsInclusive );
2235 break;
2236 }
2237
2238 case OFDT_GLOB:
2239 res = std::make_unique< QgsGlobFieldDomain >( name, description, fieldType,
2240 QString( OGR_GlobFldDomain_GetGlob( domain ) ) );
2241 break;
2242 }
2243
2244 switch ( OGR_FldDomain_GetMergePolicy( domain ) )
2245 {
2246 case OFDMP_DEFAULT_VALUE:
2247 res->setMergePolicy( Qgis::FieldDomainMergePolicy::DefaultValue );
2248 break;
2249 case OFDMP_SUM:
2250 res->setMergePolicy( Qgis::FieldDomainMergePolicy::Sum );
2251 break;
2252 case OFDMP_GEOMETRY_WEIGHTED:
2254 break;
2255 }
2256
2257 switch ( OGR_FldDomain_GetSplitPolicy( domain ) )
2258 {
2259 case OFDSP_DEFAULT_VALUE:
2260 res->setSplitPolicy( Qgis::FieldDomainSplitPolicy::DefaultValue );
2261 break;
2262 case OFDSP_DUPLICATE:
2263 res->setSplitPolicy( Qgis::FieldDomainSplitPolicy::Duplicate );
2264 break;
2265 case OFDSP_GEOMETRY_RATIO:
2266 res->setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio );
2267 break;
2268 }
2269 return res;
2270}
2271
2272OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain )
2273{
2274 if ( !domain )
2275 return nullptr;
2276
2277 OGRFieldType domainFieldType = OFTInteger;
2278 OGRFieldSubType domainFieldSubType = OFSTNone;
2279 variantTypeToOgrFieldType( domain->fieldType(), domainFieldType, domainFieldSubType );
2280
2281 OGRFieldDomainH res = nullptr;
2282 switch ( domain->type() )
2283 {
2285 {
2286 std::vector< OGRCodedValue > enumeration;
2287 const QList< QgsCodedValue> values = qgis::down_cast< const QgsCodedFieldDomain * >( domain )->values();
2288 enumeration.reserve( values.size() );
2289 for ( const QgsCodedValue &value : values )
2290 {
2291 OGRCodedValue codedValue;
2292 codedValue.pszCode = CPLStrdup( value.code().toString().toUtf8().constData() );
2293 codedValue.pszValue = CPLStrdup( value.value().toUtf8().constData() );
2294 enumeration.push_back( codedValue );
2295 }
2296 OGRCodedValue last;
2297 last.pszCode = nullptr;
2298 last.pszValue = nullptr;
2299 enumeration.push_back( last );
2300 res = OGR_CodedFldDomain_Create(
2301 domain->name().toUtf8().constData(),
2302 domain->description().toUtf8().constData(),
2303 domainFieldType,
2304 domainFieldSubType,
2305 enumeration.data()
2306 );
2307
2308 for ( const OGRCodedValue &value : std::as_const( enumeration ) )
2309 {
2310 CPLFree( value.pszCode );
2311 CPLFree( value.pszValue );
2312 }
2313 break;
2314 }
2315
2317 {
2318 std::unique_ptr< OGRField > min = variantToOGRField( qgis::down_cast< const QgsRangeFieldDomain * >( domain )->minimum(), domainFieldType );
2319 std::unique_ptr< OGRField > max = variantToOGRField( qgis::down_cast< const QgsRangeFieldDomain * >( domain )->maximum(), domainFieldType );
2320 if ( !min || !max )
2321 return nullptr;
2322 res = OGR_RangeFldDomain_Create(
2323 domain->name().toUtf8().constData(),
2324 domain->description().toUtf8().constData(),
2325 domainFieldType,
2326 domainFieldSubType,
2327 min.get(),
2328 qgis::down_cast< const QgsRangeFieldDomain * >( domain )->minimumIsInclusive(),
2329 max.get(),
2330 qgis::down_cast< const QgsRangeFieldDomain * >( domain )->maximumIsInclusive()
2331 );
2332 break;
2333 }
2334
2336 {
2337 res = OGR_GlobFldDomain_Create(
2338 domain->name().toUtf8().constData(),
2339 domain->description().toUtf8().constData(),
2340 domainFieldType,
2341 domainFieldSubType,
2342 qgis::down_cast< const QgsGlobFieldDomain * >( domain )->glob().toUtf8().constData()
2343 );
2344 break;
2345 }
2346 }
2347
2348 switch ( domain->mergePolicy() )
2349 {
2351 OGR_FldDomain_SetMergePolicy( res, OFDMP_DEFAULT_VALUE );
2352 break;
2354 OGR_FldDomain_SetMergePolicy( res, OFDMP_GEOMETRY_WEIGHTED );
2355 break;
2357 OGR_FldDomain_SetMergePolicy( res, OFDMP_SUM );
2358 break;
2359 }
2360
2361 switch ( domain->splitPolicy() )
2362 {
2364 OGR_FldDomain_SetSplitPolicy( res, OFDSP_DEFAULT_VALUE );
2365 break;
2367 OGR_FldDomain_SetSplitPolicy( res, OFDSP_GEOMETRY_RATIO );
2368 break;
2370 OGR_FldDomain_SetSplitPolicy( res, OFDSP_DUPLICATE );
2371 break;
2372
2374 // not supported
2375 break;
2376 }
2377
2378 return res;
2379}
2380
2381#endif
2382
2383#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
2384QgsWeakRelation QgsOgrUtils::convertRelationship( GDALRelationshipH relationship, const QString &datasetUri )
2385{
2386 QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
2387 const QVariantMap datasetUriParts = ogrProviderMetadata->decodeUri( datasetUri );
2388
2389 const QString leftTableName( GDALRelationshipGetLeftTableName( relationship ) );
2390
2391 QVariantMap leftTableUriParts = datasetUriParts;
2392 leftTableUriParts.insert( QStringLiteral( "layerName" ), leftTableName );
2393 const QString leftTableSource = ogrProviderMetadata->encodeUri( leftTableUriParts );
2394
2395 const QString rightTableName( GDALRelationshipGetRightTableName( relationship ) );
2396 QVariantMap rightTableUriParts = datasetUriParts;
2397 rightTableUriParts.insert( QStringLiteral( "layerName" ), rightTableName );
2398 const QString rightTableSource = ogrProviderMetadata->encodeUri( rightTableUriParts );
2399
2400 const QString mappingTableName( GDALRelationshipGetMappingTableName( relationship ) );
2401 QString mappingTableSource;
2402 if ( !mappingTableName.isEmpty() )
2403 {
2404 QVariantMap mappingTableUriParts = datasetUriParts;
2405 mappingTableUriParts.insert( QStringLiteral( "layerName" ), mappingTableName );
2406 mappingTableSource = ogrProviderMetadata->encodeUri( mappingTableUriParts );
2407 }
2408
2409 const QString relationshipName( GDALRelationshipGetName( relationship ) );
2410
2411 char **cslLeftTableFieldNames = GDALRelationshipGetLeftTableFields( relationship );
2412 const QStringList leftTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftTableFieldNames );
2413 CSLDestroy( cslLeftTableFieldNames );
2414
2415 char **cslRightTableFieldNames = GDALRelationshipGetRightTableFields( relationship );
2416 const QStringList rightTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightTableFieldNames );
2417 CSLDestroy( cslRightTableFieldNames );
2418
2419 char **cslLeftMappingTableFieldNames = GDALRelationshipGetLeftMappingTableFields( relationship );
2420 const QStringList leftMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftMappingTableFieldNames );
2421 CSLDestroy( cslLeftMappingTableFieldNames );
2422
2423 char **cslRightMappingTableFieldNames = GDALRelationshipGetRightMappingTableFields( relationship );
2424 const QStringList rightMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightMappingTableFieldNames );
2425 CSLDestroy( cslRightMappingTableFieldNames );
2426
2427 const QString forwardPathLabel( GDALRelationshipGetForwardPathLabel( relationship ) );
2428 const QString backwardPathLabel( GDALRelationshipGetBackwardPathLabel( relationship ) );
2429 const QString relatedTableType( GDALRelationshipGetRelatedTableType( relationship ) );
2430
2431 const GDALRelationshipType relationshipType = GDALRelationshipGetType( relationship );
2433 switch ( relationshipType )
2434 {
2435 case GRT_COMPOSITE:
2437 break;
2438
2439 case GRT_ASSOCIATION:
2441 break;
2442
2443 case GRT_AGGREGATION:
2444 QgsLogger::warning( "Aggregation relationships are not supported, treating as association instead" );
2445 break;
2446 }
2447
2448 const GDALRelationshipCardinality eCardinality = GDALRelationshipGetCardinality( relationship );
2450 switch ( eCardinality )
2451 {
2452 case GRC_ONE_TO_ONE:
2454 break;
2455 case GRC_ONE_TO_MANY:
2457 break;
2458 case GRC_MANY_TO_ONE:
2460 break;
2461 case GRC_MANY_TO_MANY:
2463 break;
2464 }
2465
2466 switch ( cardinality )
2467 {
2471 {
2472 QgsWeakRelation rel( relationshipName,
2473 relationshipName,
2474 strength,
2475 QString(), QString(), rightTableSource, QStringLiteral( "ogr" ),
2476 QString(), QString(), leftTableSource, QStringLiteral( "ogr" ) );
2477 rel.setCardinality( cardinality );
2478 rel.setForwardPathLabel( forwardPathLabel );
2479 rel.setBackwardPathLabel( backwardPathLabel );
2480 rel.setRelatedTableType( relatedTableType );
2481 rel.setReferencedLayerFields( leftTableFieldNames );
2482 rel.setReferencingLayerFields( rightTableFieldNames );
2483 return rel;
2484 }
2485
2487 {
2488 QgsWeakRelation rel( relationshipName,
2489 relationshipName,
2490 strength,
2491 QString(), QString(), rightTableSource, QStringLiteral( "ogr" ),
2492 QString(), QString(), leftTableSource, QStringLiteral( "ogr" ) );
2493 rel.setCardinality( cardinality );
2494 rel.setForwardPathLabel( forwardPathLabel );
2495 rel.setBackwardPathLabel( backwardPathLabel );
2496 rel.setRelatedTableType( relatedTableType );
2497 rel.setMappingTable( QgsVectorLayerRef( QString(), QString(), mappingTableSource, QStringLiteral( "ogr" ) ) );
2498 rel.setReferencedLayerFields( leftTableFieldNames );
2499 rel.setMappingReferencedLayerFields( leftMappingTableFieldNames );
2500 rel.setReferencingLayerFields( rightTableFieldNames );
2501 rel.setMappingReferencingLayerFields( rightMappingTableFieldNames );
2502 return rel;
2503 }
2504 }
2505 return QgsWeakRelation();
2506}
2507
2509{
2510 GDALRelationshipCardinality gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_MANY;
2511 switch ( relationship.cardinality() )
2512 {
2514 gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_ONE;
2515 break;
2517 gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_MANY;
2518 break;
2520 gCardinality = GDALRelationshipCardinality::GRC_MANY_TO_ONE;
2521 break;
2523 gCardinality = GDALRelationshipCardinality::GRC_MANY_TO_MANY;
2524 break;
2525 }
2526
2527 QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
2528
2529 const QVariantMap leftParts = ogrProviderMetadata->decodeUri( relationship.referencedLayerSource() );
2530 const QString leftTableName = leftParts.value( QStringLiteral( "layerName" ) ).toString();
2531 if ( leftTableName.isEmpty() )
2532 {
2533 error = QObject::tr( "Parent table name was not set" );
2534 return nullptr;
2535 }
2536
2537 const QVariantMap rightParts = ogrProviderMetadata->decodeUri( relationship.referencingLayerSource() );
2538 const QString rightTableName = rightParts.value( QStringLiteral( "layerName" ) ).toString();
2539 if ( rightTableName.isEmpty() )
2540 {
2541 error = QObject::tr( "Child table name was not set" );
2542 return nullptr;
2543 }
2544
2545 if ( leftParts.value( QStringLiteral( "path" ) ).toString() != rightParts.value( QStringLiteral( "path" ) ).toString() )
2546 {
2547 error = QObject::tr( "Parent and child table must be from the same dataset" );
2548 return nullptr;
2549 }
2550
2551 QString mappingTableName;
2552 if ( !relationship.mappingTableSource().isEmpty() )
2553 {
2554 const QVariantMap mappingParts = ogrProviderMetadata->decodeUri( relationship.mappingTableSource() );
2555 mappingTableName = mappingParts.value( QStringLiteral( "layerName" ) ).toString();
2556 if ( leftParts.value( QStringLiteral( "path" ) ).toString() != mappingParts.value( QStringLiteral( "path" ) ).toString() )
2557 {
2558 error = QObject::tr( "Parent and mapping table must be from the same dataset" );
2559 return nullptr;
2560 }
2561 }
2562
2563 gdal::relationship_unique_ptr relationH( GDALRelationshipCreate( relationship.name().toLocal8Bit().constData(),
2564 leftTableName.toLocal8Bit().constData(),
2565 rightTableName.toLocal8Bit().constData(),
2566 gCardinality ) );
2567
2568 // set left table fields
2569 const QStringList leftFieldNames = relationship.referencedLayerFields();
2570 int count = leftFieldNames.count();
2571 char **lst = new char *[count + 1];
2572 if ( count > 0 )
2573 {
2574 int pos = 0;
2575 for ( const QString &string : leftFieldNames )
2576 {
2577 lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
2578 pos++;
2579 }
2580 }
2581 lst[count] = nullptr;
2582 GDALRelationshipSetLeftTableFields( relationH.get(), lst );
2583 CSLDestroy( lst );
2584
2585 // set right table fields
2586 const QStringList rightFieldNames = relationship.referencingLayerFields();
2587 count = rightFieldNames.count();
2588 lst = new char *[count + 1];
2589 if ( count > 0 )
2590 {
2591 int pos = 0;
2592 for ( const QString &string : rightFieldNames )
2593 {
2594 lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
2595 pos++;
2596 }
2597 }
2598 lst[count] = nullptr;
2599 GDALRelationshipSetRightTableFields( relationH.get(), lst );
2600 CSLDestroy( lst );
2601
2602 if ( !mappingTableName.isEmpty() )
2603 {
2604 GDALRelationshipSetMappingTableName( relationH.get(), mappingTableName.toLocal8Bit().constData() );
2605
2606 // set left mapping table fields
2607 const QStringList leftFieldNames = relationship.mappingReferencedLayerFields();
2608 int count = leftFieldNames.count();
2609 char **lst = new char *[count + 1];
2610 if ( count > 0 )
2611 {
2612 int pos = 0;
2613 for ( const QString &string : leftFieldNames )
2614 {
2615 lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
2616 pos++;
2617 }
2618 }
2619 lst[count] = nullptr;
2620 GDALRelationshipSetLeftMappingTableFields( relationH.get(), lst );
2621 CSLDestroy( lst );
2622
2623 // set right table fields
2624 const QStringList rightFieldNames = relationship.mappingReferencingLayerFields();
2625 count = rightFieldNames.count();
2626 lst = new char *[count + 1];
2627 if ( count > 0 )
2628 {
2629 int pos = 0;
2630 for ( const QString &string : rightFieldNames )
2631 {
2632 lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
2633 pos++;
2634 }
2635 }
2636 lst[count] = nullptr;
2637 GDALRelationshipSetRightMappingTableFields( relationH.get(), lst );
2638 CSLDestroy( lst );
2639 }
2640
2641 // set type
2642 switch ( relationship.strength() )
2643 {
2645 GDALRelationshipSetType( relationH.get(), GDALRelationshipType::GRT_ASSOCIATION );
2646 break;
2647
2649 GDALRelationshipSetType( relationH.get(), GDALRelationshipType::GRT_COMPOSITE );
2650 break;
2651 }
2652
2653 // set labels
2654 if ( !relationship.forwardPathLabel().isEmpty() )
2655 GDALRelationshipSetForwardPathLabel( relationH.get(), relationship.forwardPathLabel().toLocal8Bit().constData() );
2656 if ( !relationship.backwardPathLabel().isEmpty() )
2657 GDALRelationshipSetBackwardPathLabel( relationH.get(), relationship.backwardPathLabel().toLocal8Bit().constData() );
2658
2659 // set table type
2660 if ( !relationship.relatedTableType().isEmpty() )
2661 GDALRelationshipSetRelatedTableType( relationH.get(), relationship.relatedTableType().toLocal8Bit().constData() );
2662
2663 return relationH;
2664}
2665#endif
2666
2667int QgsOgrUtils::listStyles( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause )
2668{
2669 OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" );
2670 if ( !hLayer )
2671 {
2672 QgsDebugMsgLevel( QStringLiteral( "No styles available on DB" ), 2 );
2673 errCause = QObject::tr( "No styles available on DB" );
2674 return 0;
2675 }
2676
2677 if ( OGR_L_GetFeatureCount( hLayer, TRUE ) == 0 )
2678 {
2679 QgsDebugMsgLevel( QStringLiteral( "No styles available on DB" ), 2 );
2680 errCause = QObject::tr( "No styles available on DB" );
2681 return 0;
2682 }
2683
2684 OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
2685
2686 OGR_L_ResetReading( hLayer );
2687
2688 QList<qlonglong> listTimestamp;
2689 QMap<int, QString> mapIdToStyleName;
2690 QMap<int, QString> mapIdToDescription;
2691 QMap<qlonglong, QList<int> > mapTimestampToId;
2692 int numberOfRelatedStyles = 0;
2693
2694 while ( true )
2695 {
2696 gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) );
2697 if ( !hFeature )
2698 break;
2699
2700 QString tableName( QString::fromUtf8(
2701 OGR_F_GetFieldAsString( hFeature.get(),
2702 OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ) ) ) );
2703 QString geometryColumn( QString::fromUtf8(
2704 OGR_F_GetFieldAsString( hFeature.get(),
2705 OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ) ) ) );
2706 QString styleName( QString::fromUtf8(
2707 OGR_F_GetFieldAsString( hFeature.get(),
2708 OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ) );
2709 QString description( QString::fromUtf8(
2710 OGR_F_GetFieldAsString( hFeature.get(),
2711 OGR_FD_GetFieldIndex( hLayerDefn, "description" ) ) ) );
2712 int fid = static_cast<int>( OGR_F_GetFID( hFeature.get() ) );
2713 if ( tableName == layerName &&
2714 geometryColumn == geomColumn )
2715 {
2716 // Append first all related styles
2717 QString id( QString::number( fid ) );
2718 ids.append( id );
2719 names.append( styleName );
2720 descriptions.append( description );
2721 ++ numberOfRelatedStyles;
2722 }
2723 else
2724 {
2725 int year, month, day, hour, minute, second, TZ;
2726 OGR_F_GetFieldAsDateTime( hFeature.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ),
2727 &year, &month, &day, &hour, &minute, &second, &TZ );
2728 const qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 +
2729 static_cast<qlonglong>( month ) * 31 * 24 * 3600 + static_cast<qlonglong>( year ) * 12 * 31 * 24 * 3600;
2730
2731 listTimestamp.append( ts );
2732 mapIdToStyleName[fid] = styleName;
2733 mapIdToDescription[fid] = description;
2734 mapTimestampToId[ts].append( fid );
2735 }
2736 }
2737
2738 std::sort( listTimestamp.begin(), listTimestamp.end() );
2739 // Sort from most recent to least recent
2740 for ( int i = listTimestamp.size() - 1; i >= 0; i-- )
2741 {
2742 const QList<int> &listId = mapTimestampToId[listTimestamp[i]];
2743 for ( int j = 0; j < listId.size(); j++ )
2744 {
2745 int fid = listId[j];
2746 QString id( QString::number( fid ) );
2747 ids.append( id );
2748 names.append( mapIdToStyleName[fid] );
2749 descriptions.append( mapIdToDescription[fid] );
2750 }
2751 }
2752
2753 return numberOfRelatedStyles;
2754}
2755
2756bool QgsOgrUtils::styleExists( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &styleId, QString &errorCause )
2757{
2758 errorCause.clear();
2759
2760 // check if layer_styles table exists
2761 OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" );
2762 if ( !hLayer )
2763 return false;
2764
2765 const QString realStyleId = styleId.isEmpty() ? layerName : styleId;
2766
2767 const QString checkQuery = QStringLiteral( "f_table_schema=''"
2768 " AND f_table_name=%1"
2769 " AND f_geometry_column=%2"
2770 " AND styleName=%3" )
2771 .arg( QgsOgrProviderUtils::quotedValue( layerName ),
2772 QgsOgrProviderUtils::quotedValue( geomColumn ),
2773 QgsOgrProviderUtils::quotedValue( realStyleId ) );
2774 OGR_L_SetAttributeFilter( hLayer, checkQuery.toUtf8().constData() );
2775 OGR_L_ResetReading( hLayer );
2776 gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) );
2777 OGR_L_ResetReading( hLayer );
2778
2779 if ( hFeature )
2780 return true;
2781
2782 return false;
2783}
2784
2785QString QgsOgrUtils::getStyleById( GDALDatasetH hDS, const QString &styleId, QString &errCause )
2786{
2787 OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" );
2788 if ( !hLayer )
2789 {
2790 QgsDebugMsgLevel( QStringLiteral( "No styles available on DB" ), 2 );
2791 errCause = QObject::tr( "No styles available on DB" );
2792 return QString();
2793 }
2794
2795 bool ok;
2796 int id = styleId.toInt( &ok );
2797 if ( !ok )
2798 {
2799 errCause = QObject::tr( "Invalid style identifier" );
2800 return QString();
2801 }
2802
2803 gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetFeature( hLayer, id ) );
2804 if ( !hFeature )
2805 {
2806 errCause = QObject::tr( "No style corresponding to style identifier" );
2807 return QString();
2808 }
2809
2810 OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
2811 QString styleQML( QString::fromUtf8(
2812 OGR_F_GetFieldAsString( hFeature.get(),
2813 OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ) );
2814 OGR_L_ResetReading( hLayer );
2815
2816 return styleQML;
2817}
2818
2819bool QgsOgrUtils::deleteStyleById( GDALDatasetH hDS, const QString &styleId, QString &errCause )
2820{
2821 bool deleted;
2822
2823 OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" );
2824
2825 // check if layer_styles table already exist
2826 if ( !hLayer )
2827 {
2828 errCause = QObject::tr( "Connection to database failed" );
2829 deleted = false;
2830 }
2831 else
2832 {
2833 if ( OGR_L_DeleteFeature( hLayer, styleId.toInt() ) != OGRERR_NONE )
2834 {
2835 errCause = QObject::tr( "Error executing the delete query." );
2836 deleted = false;
2837 }
2838 else
2839 {
2840 deleted = true;
2841 }
2842 }
2843 return deleted;
2844}
2845
2846QString QgsOgrUtils::loadStoredStyle( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QString &styleName, QString &errCause )
2847{
2848 OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" );
2849 if ( !hLayer )
2850 {
2851 QgsDebugMsgLevel( QStringLiteral( "No styles available on DB" ), 2 );
2852 errCause = QObject::tr( "No styles available on DB" );
2853 return QString();
2854 }
2855
2856 QString selectQmlQuery = QStringLiteral( "f_table_schema=''"
2857 " AND f_table_name=%1"
2858 " AND f_geometry_column=%2"
2859 " ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END"
2860 ",update_time DESC" )
2861 .arg( QgsOgrProviderUtils::quotedValue( layerName ),
2862 QgsOgrProviderUtils::quotedValue( geomColumn ) );
2863 OGR_L_SetAttributeFilter( hLayer, selectQmlQuery.toUtf8().constData() );
2864 OGR_L_ResetReading( hLayer );
2865 OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
2866 QString styleQML;
2867 qlonglong moreRecentTimestamp = 0;
2868 while ( true )
2869 {
2870 gdal::ogr_feature_unique_ptr hFeat( OGR_L_GetNextFeature( hLayer ) );
2871 if ( !hFeat )
2872 break;
2873 if ( OGR_F_GetFieldAsInteger( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ) ) )
2874 {
2875 styleQML = QString::fromUtf8(
2876 OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) );
2877 styleName = QString::fromUtf8(
2878 OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) );
2879 break;
2880 }
2881
2882 int year, month, day, hour, minute, second, TZ;
2883 OGR_F_GetFieldAsDateTime( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ),
2884 &year, &month, &day, &hour, &minute, &second, &TZ );
2885 qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 +
2886 static_cast<qlonglong>( month ) * 31 * 24 * 3600 + static_cast<qlonglong>( year ) * 12 * 31 * 24 * 3600;
2887 if ( ts > moreRecentTimestamp )
2888 {
2889 moreRecentTimestamp = ts;
2890 styleQML = QString::fromUtf8(
2891 OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) );
2892 styleName = QString::fromUtf8(
2893 OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) );
2894 }
2895 }
2896 OGR_L_ResetReading( hLayer );
2897
2898 return styleQML;
2899}
2900
2902 GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &qmlStyle, const QString &sldStyle,
2903 const QString &styleName, const QString &styleDescription,
2904 const QString &uiFileContent, bool useAsDefault, QString &errCause
2905)
2906{
2907 // check if layer_styles table already exist
2908 OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" );
2909 if ( !hLayer )
2910 {
2911 // if not create it
2912 // Note: we use the same schema as in the SpatiaLite and postgres providers
2913 //for cross interoperability
2914
2915 char **options = nullptr;
2916 // TODO: might need change if other drivers than GPKG / SQLite
2917 options = CSLSetNameValue( options, "FID", "id" );
2918 hLayer = GDALDatasetCreateLayer( hDS, "layer_styles", nullptr, wkbNone, options );
2919 QgsOgrProviderUtils::invalidateCachedDatasets( QString::fromUtf8( GDALGetDescription( hDS ) ) );
2920 CSLDestroy( options );
2921 if ( !hLayer )
2922 {
2923 errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." );
2924 return false;
2925 }
2926 bool ok = true;
2927 {
2928 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_catalog", OFTString ) );
2929 OGR_Fld_SetWidth( fld.get(), 256 );
2930 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2931 }
2932 {
2933 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_schema", OFTString ) );
2934 OGR_Fld_SetWidth( fld.get(), 256 );
2935 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2936 }
2937 {
2938 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_name", OFTString ) );
2939 OGR_Fld_SetWidth( fld.get(), 256 );
2940 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2941 }
2942 {
2943 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_geometry_column", OFTString ) );
2944 OGR_Fld_SetWidth( fld.get(), 256 );
2945 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2946 }
2947 {
2948 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleName", OFTString ) );
2949 OGR_Fld_SetWidth( fld.get(), 30 );
2950 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2951 }
2952 {
2953 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleQML", OFTString ) );
2954 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2955 }
2956 {
2957 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleSLD", OFTString ) );
2958 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2959 }
2960 {
2961 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "useAsDefault", OFTInteger ) );
2962 OGR_Fld_SetSubType( fld.get(), OFSTBoolean );
2963 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2964 }
2965 {
2966 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "description", OFTString ) );
2967 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2968 }
2969 {
2970 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "owner", OFTString ) );
2971 OGR_Fld_SetWidth( fld.get(), 30 );
2972 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2973 }
2974 {
2975 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "ui", OFTString ) );
2976 OGR_Fld_SetWidth( fld.get(), 30 );
2977 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2978 }
2979 {
2980 gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "update_time", OFTDateTime ) );
2981 OGR_Fld_SetDefault( fld.get(), "CURRENT_TIMESTAMP" );
2982 ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE;
2983 }
2984 if ( !ok )
2985 {
2986 errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." );
2987 return false;
2988 }
2989 }
2990
2991 QString realStyleName =
2992 styleName.isEmpty() ? layerName : styleName;
2993
2994 OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
2995
2996 if ( useAsDefault )
2997 {
2998 QString oldDefaultQuery = QStringLiteral( "useAsDefault = 1 AND f_table_schema=''"
2999 " AND f_table_name=%1"
3000 " AND f_geometry_column=%2" )
3001 .arg( QgsOgrProviderUtils::quotedValue( layerName ) )
3002 .arg( QgsOgrProviderUtils::quotedValue( geomColumn ) );
3003 OGR_L_SetAttributeFilter( hLayer, oldDefaultQuery.toUtf8().constData() );
3004 gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) );
3005 if ( hFeature )
3006 {
3007 OGR_F_SetFieldInteger( hFeature.get(),
3008 OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ),
3009 0 );
3010 bool ok = OGR_L_SetFeature( hLayer, hFeature.get() ) == 0;
3011 if ( !ok )
3012 {
3013 QgsDebugError( QStringLiteral( "Could not unset previous useAsDefault style" ) );
3014 }
3015 }
3016 }
3017
3018 QString checkQuery = QStringLiteral( "f_table_schema=''"
3019 " AND f_table_name=%1"
3020 " AND f_geometry_column=%2"
3021 " AND styleName=%3" )
3022 .arg( QgsOgrProviderUtils::quotedValue( layerName ) )
3023 .arg( QgsOgrProviderUtils::quotedValue( geomColumn ) )
3024 .arg( QgsOgrProviderUtils::quotedValue( realStyleName ) );
3025 OGR_L_SetAttributeFilter( hLayer, checkQuery.toUtf8().constData() );
3026 OGR_L_ResetReading( hLayer );
3027 gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) );
3028 OGR_L_ResetReading( hLayer );
3029 bool bNew = true;
3030
3031 if ( hFeature )
3032 {
3033 bNew = false;
3034 }
3035 else
3036 {
3037 hFeature.reset( OGR_F_Create( hLayerDefn ) );
3038 OGR_F_SetFieldString( hFeature.get(),
3039 OGR_FD_GetFieldIndex( hLayerDefn, "f_table_catalog" ),
3040 "" );
3041 OGR_F_SetFieldString( hFeature.get(),
3042 OGR_FD_GetFieldIndex( hLayerDefn, "f_table_schema" ),
3043 "" );
3044 OGR_F_SetFieldString( hFeature.get(),
3045 OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ),
3046 layerName.toUtf8().constData() );
3047 OGR_F_SetFieldString( hFeature.get(),
3048 OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ),
3049 geomColumn.toUtf8().constData() );
3050 OGR_F_SetFieldString( hFeature.get(),
3051 OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ),
3052 realStyleName.toUtf8().constData() );
3053 if ( !uiFileContent.isEmpty() )
3054 {
3055 OGR_F_SetFieldString( hFeature.get(),
3056 OGR_FD_GetFieldIndex( hLayerDefn, "ui" ),
3057 uiFileContent.toUtf8().constData() );
3058 }
3059 }
3060 OGR_F_SetFieldString( hFeature.get(),
3061 OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ),
3062 qmlStyle.toUtf8().constData() );
3063 OGR_F_SetFieldString( hFeature.get(),
3064 OGR_FD_GetFieldIndex( hLayerDefn, "styleSLD" ),
3065 sldStyle.toUtf8().constData() );
3066 OGR_F_SetFieldInteger( hFeature.get(),
3067 OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ),
3068 useAsDefault ? 1 : 0 );
3069 OGR_F_SetFieldString( hFeature.get(),
3070 OGR_FD_GetFieldIndex( hLayerDefn, "description" ),
3071 ( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ).toUtf8().constData() );
3072 OGR_F_SetFieldString( hFeature.get(),
3073 OGR_FD_GetFieldIndex( hLayerDefn, "owner" ),
3074 "" );
3075
3076 bool bFeatureOK;
3077 if ( bNew )
3078 bFeatureOK = OGR_L_CreateFeature( hLayer, hFeature.get() ) == OGRERR_NONE;
3079 else
3080 bFeatureOK = OGR_L_SetFeature( hLayer, hFeature.get() ) == OGRERR_NONE;
3081
3082 if ( !bFeatureOK )
3083 {
3084 QgsMessageLog::logMessage( QObject::tr( "Error updating style" ) );
3085 errCause = QObject::tr( "Error looking for style. The query was logged" );
3086 return false;
3087 }
3088
3089 return true;
3090}
RelationshipStrength
Relationship strength.
Definition qgis.h:3811
@ Composition
Fix relation, related elements are part of the parent and a parent copy will copy any children or del...
@ Association
Loose relation, related elements are not part of the parent and a parent copy will not copy any child...
@ GeometryWeighted
New values are computed as the weighted average of the source values.
@ DefaultValue
Use default field value.
@ GeometryRatio
New values are computed by the ratio of their area/length compared to the area/length of the original...
@ UnsetField
Clears the field value so that the data provider backend will populate using any backend triggers or ...
@ DefaultValue
Use default field value.
@ Duplicate
Duplicate original value.
MarkerShape
Marker shapes.
Definition qgis.h:2628
@ Line
Vertical line.
@ Triangle
Triangle.
@ Cross2
Rotated cross (lines only), 'x' shape.
@ Cross
Cross (lines only)
RenderUnit
Rendering size units.
Definition qgis.h:4489
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ MapUnits
Map units.
@ Coded
Coded field domain.
@ Range
Numeric range field domain (min/max)
@ Glob
Glob string pattern field domain.
RelationshipCardinality
Relationship cardinality.
Definition qgis.h:3823
@ ManyToMany
Many to many relationship.
@ ManyToOne
Many to one relationship.
@ OneToOne
One to one relationship.
@ OneToMany
One to many relationship.
SymbolType
Symbol types.
Definition qgis.h:420
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:201
@ LineString25D
LineString25D.
@ MultiSurfaceM
MultiSurfaceM.
@ MultiLineStringZM
MultiLineStringZM.
@ MultiPointZM
MultiPointZM.
@ MultiPointZ
MultiPointZ.
@ CompoundCurve
CompoundCurve.
@ MultiPolygonZM
MultiPolygonZM.
@ LineStringM
LineStringM.
@ LineString
LineString.
@ MultiLineStringM
MultiLineStringM.
@ MultiPointM
MultiPointM.
@ MultiPoint
MultiPoint.
@ LineStringZM
LineStringZM.
@ GeometryCollectionZM
GeometryCollectionZM.
@ TriangleZ
TriangleZ.
@ Polygon
Polygon.
@ CompoundCurveZM
CompoundCurveZM.
@ CompoundCurveM
CompoundCurveM.
@ MultiPolygon
MultiPolygon.
@ GeometryCollectionZ
GeometryCollectionZ.
@ GeometryCollectionM
GeometryCollectionM.
@ CircularStringZM
CircularStringZM.
@ Triangle
Triangle.
@ PolygonM
PolygonM.
@ NoGeometry
No geometry.
@ MultiSurfaceZ
MultiSurfaceZ.
@ CurvePolygonZM
CurvePolygonZM.
@ MultiLineString
MultiLineString.
@ MultiPolygonM
MultiPolygonM.
@ MultiCurveZM
MultiCurveZM.
@ MultiSurfaceZM
MultiSurfaceZM.
@ PolygonZM
PolygonZM.
@ Unknown
Unknown.
@ PointM
PointM.
@ CurvePolygonM
CurvePolygonM.
@ CircularString
CircularString.
@ PointZ
PointZ.
@ TriangleZM
TriangleZM.
@ MultiLineStringZ
MultiLineStringZ.
@ GeometryCollection
GeometryCollection.
@ MultiPolygonZ
MultiPolygonZ.
@ CurvePolygonZ
CurvePolygonZ.
@ MultiCurve
MultiCurve.
@ CompoundCurveZ
CompoundCurveZ.
@ MultiCurveZ
MultiCurveZ.
@ MultiCurveM
MultiCurveM.
@ CircularStringM
CircularStringM.
@ CurvePolygon
CurvePolygon.
@ PointZM
PointZM.
@ TriangleM
TriangleM.
@ CircularStringZ
CircularStringZ.
@ LineStringZ
LineStringZ.
@ MultiSurface
MultiSurface.
@ PolygonZ
PolygonZ.
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
static QgsFontManager * fontManager()
Returns the application font manager, which manages available fonts and font installation for the QGI...
static endian_t endian()
Returns whether this machine uses big or little endian.
Associates a code and a value.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
void setCoordinateEpoch(double epoch)
Sets the coordinate epoch, as a decimal year.
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
double coordinateEpoch() const
Returns the coordinate epoch, as a decimal year.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
void setId(QgsFeatureId id)
Sets the feature id for this feature.
void clearGeometry()
Removes any geometry associated with the feature.
void setValid(bool validity)
Sets the validity of the feature.
bool isValid() const
Returns the validity of this feature.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Base class for field domains.
Qgis::FieldDomainMergePolicy mergePolicy() const
Returns the merge policy.
Qgis::FieldDomainSplitPolicy splitPolicy() const
Returns the split policy.
QMetaType::Type fieldType() const
Returns the associated field type.
virtual Qgis::FieldDomainType type() const =0
Returns the type of field domain.
QString name() const
Returns the name of the field domain.
QString description() const
Returns the description of the field domain.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
QMetaType::Type type
Definition qgsfield.h:60
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
Definition qgsfield.cpp:155
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:60
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
static bool fontFamilyMatchOnSystem(const QString &family, QString *chosen=nullptr, bool *match=nullptr)
Check whether font family is on system.
A geometry is the spatial representation of a feature.
void fromWkb(unsigned char *wkb, int length)
Set the geometry, feeding in the buffer containing OGC Well-Known Binary and the buffer's length.
static void warning(const QString &msg)
Goes to qWarning.
Context for a MapInfo symbol conversion operation.
static QgsFillSymbol * convertFillSymbol(int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &foreColor, const QColor &backColor=QColor())
Converts the MapInfo fill symbol with the specified identifier to a QgsFillSymbol.
static QgsMarkerSymbol * convertMarkerSymbol(int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &color, double size, Qgis::RenderUnit sizeUnit)
Converts the MapInfo marker symbol with the specified identifier to a QgsMarkerSymbol.
static QgsLineSymbol * convertLineSymbol(int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &foreColor, double size, Qgis::RenderUnit sizeUnit, bool interleaved=false)
Converts the MapInfo line symbol with the specified identifier to a QgsLineSymbol.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QString readShapefileEncoding(const QString &path)
Reads the encoding of the shapefile at the specified path (where path is the location of the "....
static QgsWeakRelation convertRelationship(GDALRelationshipH relationship, const QString &datasetUri)
Converts an GDAL relationship definition to a QgsWeakRelation equivalent.
static QVariant stringToVariant(OGRFieldType type, OGRFieldSubType subType, const QString &string)
Converts a string to a variant, using the provider OGR field type and subType to determine the most a...
static bool readOgrFeatureAttributes(OGRFeatureH ogrFet, const QgsFields &fields, QgsFeature &feature, QTextCodec *encoding)
Reads all attributes from an OGR feature into a QgsFeature.
static QString loadStoredStyle(GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QString &styleName, QString &errCause)
Helper function for loading a stored styles in ogr/gdal database datasources.
static QString getStyleById(GDALDatasetH hDS, const QString &styleId, QString &errCause)
Helper function for getting a style by ID from ogr/gdal database datasources.
static Qgis::WkbType ogrGeometryTypeToQgsWkbType(OGRwkbGeometryType ogrGeomType)
Converts a OGRwkbGeometryType to QgsWkbTypes::Type.
static int listStyles(GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause)
Helper function for listing styles in ogr/gdal database datasources.
static QgsGeometry ogrGeometryToQgsGeometry(OGRGeometryH geom)
Converts an OGR geometry representation to a QgsGeometry object.
static QString OGRSpatialReferenceToWkt(OGRSpatialReferenceH srs)
Returns a WKT string corresponding to the specified OGR srs object.
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
static bool saveStyle(GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &qmlStyle, const QString &sldStyle, const QString &styleName, const QString &styleDescription, const QString &uiFileContent, bool useAsDefault, QString &errCause)
Helper function for saving a style to ogr/gdal database datasources.
static bool styleExists(GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &styleId, QString &errorCause)
Helper function for checking whether a style exists in ogr/gdal database datasources.
static QVariant OGRFieldtoVariant(const OGRField *value, OGRFieldType type)
Converts an OGRField value of the specified type into a QVariant.
static QgsFeature readOgrFeature(OGRFeatureH ogrFet, const QgsFields &fields, QTextCodec *encoding)
Reads an OGR feature and converts it to a QgsFeature.
static QStringList cStringListToQStringList(char **stringList)
Converts a c string list to a QStringList.
static void ogrFieldTypeToQVariantType(OGRFieldType ogrType, OGRFieldSubType ogrSubType, QMetaType::Type &variantType, QMetaType::Type &variantSubType)
Converts an OGR field type and sub type to the best matching QVariant::Type equivalent.
static QgsFields readOgrFields(OGRFeatureH ogrFet, QTextCodec *encoding)
Reads an OGR feature and returns a corresponding fields collection.
static QList< QgsVectorDataProvider::NativeType > nativeFieldTypesForDriver(GDALDriverH driver)
Returns the list of native field types supported for a driver.
static std::unique_ptr< QgsFieldDomain > convertFieldDomain(OGRFieldDomainH domain)
Converts an OGR field domain definition to a QgsFieldDomain equivalent.
static QgsCoordinateReferenceSystem OGRSpatialReferenceToCrs(OGRSpatialReferenceH srs)
Returns a QgsCoordinateReferenceSystem corresponding to the specified OGR srs object,...
static QgsFeatureList stringToFeatureList(const QString &string, const QgsFields &fields, QTextCodec *encoding)
Attempts to parse a string representing a collection of features using OGR.
static QString readShapefileEncodingFromCpg(const QString &path)
Reads the encoding of the shapefile at the specified path (where path is the location of the "....
static bool readOgrFeatureGeometry(OGRFeatureH ogrFet, QgsFeature &feature)
Reads the geometry from an OGR feature into a QgsFeature.
static QgsFields stringToFields(const QString &string, QTextCodec *encoding)
Attempts to retrieve the fields from a string representing a collection of features using OGR.
static QString readShapefileEncodingFromLdid(const QString &path)
Reads the encoding of the shapefile at the specified path (where path is the location of the "....
static std::unique_ptr< QgsSymbol > symbolFromStyleString(const QString &string, Qgis::SymbolType type)
Creates a new QgsSymbol matching an OGR style string.
static std::unique_ptr< OGRField > variantToOGRField(const QVariant &value, OGRFieldType type)
Converts a QVariant to an OGRField value of specified type.
static bool deleteStyleById(GDALDatasetH hDS, const QString &styleId, QString &errCause)
Helper function for deleting a style by id from ogr/gdal database datasources.
static void variantTypeToOgrFieldType(QMetaType::Type variantType, OGRFieldType &ogrType, OGRFieldSubType &ogrSubType)
Converts an QVariant type to the best matching OGR field type and sub type.
static QVariantMap parseStyleString(const QString &string)
Parses an OGR style string to a variant map containing the style string components.
static int OGRTZFlagFromQt(const QDateTime &datetime)
Gets the value of OGRField::Date::TZFlag from the timezone of a QDateTime.
static QVariant getOgrFeatureAttribute(OGRFeatureH ogrFet, const QgsFields &fields, int attIndex, QTextCodec *encoding, bool *ok=nullptr)
Retrieves an attribute value from an OGR feature.
Holds data provider key, description, and associated shared library file or function pointer informat...
virtual QString encodeUri(const QVariantMap &parts) const
Reassembles a provider data source URI from its component paths (e.g.
virtual QVariantMap decodeUri(const QString &uri) const
Breaks a provider data source URI into its component paths (e.g.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
static bool shapeIsFilled(Qgis::MarkerShape shape)
Returns true if a symbol shape has a fill.
static bool condenseFillAndOutline(QgsFillSymbolLayer *fill, QgsLineSymbolLayer *outline)
Attempts to condense a fill and outline layer, by moving the outline layer to the fill symbol's strok...
static QString typeToDisplayString(QMetaType::Type type, QMetaType::Type subType=QMetaType::Type::UnknownType)
Returns a user-friendly translated string representing a QVariant type.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
The QgsWeakRelation class represent a QgsRelation with possibly unresolved layers or unmatched fields...
void setMappingTable(const QgsVectorLayerRef &table)
Sets a weak reference to the mapping table, which forms the middle table in many-to-many relationship...
QStringList mappingReferencingLayerFields() const
Returns the list of fields from the mappingTable() involved in the relationship.
void setForwardPathLabel(const QString &label)
Sets the label of the forward path for the relationship.
QString mappingTableSource() const
Returns the source URI for the mapping table, which forms the middle table in many-to-many relationsh...
void setMappingReferencingLayerFields(const QStringList &fields)
Sets the list of fields from the mappingTable() involved in the relationship.
void setBackwardPathLabel(const QString &label)
Sets the label of the backward path for the relationship.
QString relatedTableType() const
Returns the type string of the related table.
void setReferencingLayerFields(const QStringList &fields)
Sets the list of fields from the referencingLayer() involved in the relationship.
QString name() const
Returns the relationship's name.
QString backwardPathLabel() const
Returns the label of the backward path for the relationship.
void setMappingReferencedLayerFields(const QStringList &fields)
Sets the list of fields from the mappingTable() involved in the relationship.
QString referencedLayerSource() const
Returns the source URI for the referenced (or "parent" / "left") layer.
QString referencingLayerSource() const
Returns the source URI for the referencing (or "child" / "right") layer.
QString forwardPathLabel() const
Returns the label of the forward path for the relationship.
Qgis::RelationshipCardinality cardinality() const
Returns the relationship's cardinality.
void setCardinality(Qgis::RelationshipCardinality cardinality)
Sets the relationship's cardinality.
QStringList referencedLayerFields() const
Returns the list of fields from the referencedLayer() involved in the relationship.
QStringList mappingReferencedLayerFields() const
Returns the list of fields from the mappingTable() involved in the relationship.
void setRelatedTableType(const QString &type)
Sets the type string of the related table.
QStringList referencingLayerFields() const
Returns the list of fields from the referencingLayer() involved in the relationship.
void setReferencedLayerFields(const QStringList &fields)
Sets the list of fields from the referencedLayer() involved in the relationship.
Qgis::RelationshipStrength strength() const
Returns the strength of the relation.
static Qgis::WkbType zmType(Qgis::WkbType type, bool hasZ, bool hasM)
Returns the modified input geometry type according to hasZ / hasM.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
void CORE_EXPORT fast_delete_and_close(dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path)
Performs a fast close of an unwanted GDAL dataset handle by deleting the underlying data store.
std::unique_ptr< std::remove_pointer< OGRFeatureH >::type, OGRFeatureDeleter > ogr_feature_unique_ptr
Scoped OGR feature.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
std::unique_ptr< std::remove_pointer< GDALRelationshipH >::type, GDALRelationshipDeleter > relationship_unique_ptr
Scoped GDAL relationship.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:5993
void * GDALDatasetH
void * OGRSpatialReferenceH
QList< QgsFeature > QgsFeatureList
#define DEFAULT_SIMPLELINE_WIDTH
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
#define DEFAULT_SIMPLEMARKER_SIZE
std::unique_ptr< QgsLineString > ogrGeometryToQgsLineString(OGRGeometryH geom)
std::unique_ptr< QgsMultiLineString > ogrGeometryToQgsMultiLineString(OGRGeometryH geom)
std::unique_ptr< QgsMultiPoint > ogrGeometryToQgsMultiPoint(OGRGeometryH geom)
std::unique_ptr< QgsPolygon > ogrGeometryToQgsPolygon(OGRGeometryH geom)
std::unique_ptr< QgsPoint > ogrGeometryToQgsPoint(OGRGeometryH geom)
std::unique_ptr< QgsMultiPolygon > ogrGeometryToQgsMultiPolygon(OGRGeometryH geom)
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
_LayerRef< QgsVectorLayer > QgsVectorLayerRef
const QgsCoordinateReferenceSystem & crs
void CORE_EXPORT operator()(GDALDatasetH datasource) const
Destroys an gdal dataset, using the correct gdal calls.
void CORE_EXPORT operator()(GDALRelationshipH relationship) const
Destroys GDAL relationship, using the correct gdal calls.
void CORE_EXPORT operator()(GDALWarpOptions *options) const
Destroys GDAL warp options, using the correct gdal calls.
void CORE_EXPORT operator()(OGRDataSourceH source) const
Destroys an OGR data source, using the correct gdal calls.
void CORE_EXPORT operator()(OGRFeatureH feature) const
Destroys an OGR feature, using the correct gdal calls.
void CORE_EXPORT operator()(OGRFieldDefnH definition) const
Destroys an OGR field definition, using the correct gdal calls.
void CORE_EXPORT operator()(OGRGeometryH geometry) const
Destroys an OGR geometry, using the correct gdal calls.