mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-06 00:07:29 -04:00
2669 lines
86 KiB
C++
2669 lines
86 KiB
C++
/***************************************************************************
|
|
qgsogrutils.cpp
|
|
---------------
|
|
begin : February 2016
|
|
copyright : (C) 2016 Nyall Dawson
|
|
email : nyall dot dawson at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgsogrutils.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgsfields.h"
|
|
#include "qgslinestring.h"
|
|
#include "qgsmultipoint.h"
|
|
#include "qgsmultilinestring.h"
|
|
#include "qgslinesymbollayer.h"
|
|
#include "qgspolygon.h"
|
|
#include "qgsmultipolygon.h"
|
|
#include "qgsmapinfosymbolconverter.h"
|
|
#include "qgsfillsymbollayer.h"
|
|
#include "qgsmarkersymbollayer.h"
|
|
#include "qgssymbollayerutils.h"
|
|
#include "qgsfontutils.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgssymbol.h"
|
|
#include "qgsfillsymbol.h"
|
|
#include "qgslinesymbol.h"
|
|
#include "qgsmarkersymbol.h"
|
|
#include "qgsfielddomain.h"
|
|
#include "qgsfontmanager.h"
|
|
#include "qgsvariantutils.h"
|
|
#include "qgsogrproviderutils.h"
|
|
|
|
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
|
|
#include "qgsweakrelation.h"
|
|
#include "qgsproviderregistry.h"
|
|
#include "qgsprovidermetadata.h"
|
|
#endif
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <QTextCodec>
|
|
#include <QUuid>
|
|
#include <cpl_error.h>
|
|
#include <QJsonDocument>
|
|
#include <QFileInfo>
|
|
#include <QDir>
|
|
#include <QTextStream>
|
|
#include <QDataStream>
|
|
#include <QRegularExpression>
|
|
|
|
#include "ogr_srs_api.h"
|
|
|
|
|
|
void gdal::OGRDataSourceDeleter::operator()( OGRDataSourceH source ) const
|
|
{
|
|
OGR_DS_Destroy( source );
|
|
}
|
|
|
|
|
|
void gdal::OGRGeometryDeleter::operator()( OGRGeometryH geometry ) const
|
|
{
|
|
OGR_G_DestroyGeometry( geometry );
|
|
}
|
|
|
|
void gdal::OGRFldDeleter::operator()( OGRFieldDefnH definition ) const
|
|
{
|
|
OGR_Fld_Destroy( definition );
|
|
}
|
|
|
|
void gdal::OGRFeatureDeleter::operator()( OGRFeatureH feature ) const
|
|
{
|
|
OGR_F_Destroy( feature );
|
|
}
|
|
|
|
void gdal::GDALDatasetCloser::operator()( GDALDatasetH dataset ) const
|
|
{
|
|
GDALClose( dataset );
|
|
}
|
|
|
|
void gdal::fast_delete_and_close( gdal::dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path )
|
|
{
|
|
// see https://github.com/qgis/QGIS/commit/d024910490a39e65e671f2055c5b6543e06c7042#commitcomment-25194282
|
|
// faster if we close the handle AFTER delete, but doesn't work for windows
|
|
#ifdef Q_OS_WIN
|
|
// close dataset handle
|
|
dataset.reset();
|
|
#endif
|
|
|
|
CPLPushErrorHandler( CPLQuietErrorHandler );
|
|
GDALDeleteDataset( driver, path.toUtf8().constData() );
|
|
CPLPopErrorHandler();
|
|
|
|
#ifndef Q_OS_WIN
|
|
// close dataset handle
|
|
dataset.reset();
|
|
#endif
|
|
}
|
|
|
|
|
|
void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options ) const
|
|
{
|
|
GDALDestroyWarpOptions( options );
|
|
}
|
|
|
|
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
|
|
void gdal::GDALRelationshipDeleter::operator()( GDALRelationshipH relationship ) const
|
|
{
|
|
GDALDestroyRelationship( relationship );
|
|
}
|
|
#endif
|
|
|
|
static void setQTTimeZoneFromOGRTZFlag( QDateTime &dt, int nTZFlag )
|
|
{
|
|
// Take into account time zone
|
|
if ( nTZFlag == 0 )
|
|
{
|
|
// unknown time zone
|
|
}
|
|
else if ( nTZFlag == 1 )
|
|
{
|
|
dt.setTimeSpec( Qt::LocalTime );
|
|
}
|
|
else if ( nTZFlag == 100 )
|
|
{
|
|
dt.setTimeSpec( Qt::UTC );
|
|
}
|
|
else
|
|
{
|
|
// TZFlag = 101 ==> UTC+00:15
|
|
// TZFlag = 99 ==> UTC-00:15
|
|
dt.setOffsetFromUtc( ( nTZFlag - 100 ) * 15 * 60 );
|
|
}
|
|
}
|
|
|
|
QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType type )
|
|
{
|
|
if ( !value || OGR_RawField_IsUnset( value ) || OGR_RawField_IsNull( value ) )
|
|
return QVariant();
|
|
|
|
switch ( type )
|
|
{
|
|
case OFTInteger:
|
|
return value->Integer;
|
|
|
|
case OFTInteger64:
|
|
return value->Integer64;
|
|
|
|
case OFTReal:
|
|
return value->Real;
|
|
|
|
case OFTString:
|
|
case OFTWideString:
|
|
return QString::fromUtf8( value->String );
|
|
|
|
case OFTDate:
|
|
return QDate( value->Date.Year, value->Date.Month, value->Date.Day );
|
|
|
|
case OFTTime:
|
|
{
|
|
float secondsPart = 0;
|
|
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
|
|
return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
|
|
}
|
|
|
|
case OFTDateTime:
|
|
{
|
|
float secondsPart = 0;
|
|
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
|
|
QDateTime dt = QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
|
|
QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
|
|
setQTTimeZoneFromOGRTZFlag( dt, value->Date.TZFlag );
|
|
return dt;
|
|
}
|
|
|
|
case OFTBinary:
|
|
// not supported!
|
|
Q_ASSERT_X( false, "QgsOgrUtils::OGRFieldtoVariant", "OFTBinary type not supported" );
|
|
return QVariant();
|
|
|
|
case OFTIntegerList:
|
|
{
|
|
QVariantList res;
|
|
res.reserve( value->IntegerList.nCount );
|
|
for ( int i = 0; i < value->IntegerList.nCount; ++i )
|
|
res << value->IntegerList.paList[ i ];
|
|
return res;
|
|
}
|
|
|
|
case OFTInteger64List:
|
|
{
|
|
QVariantList res;
|
|
res.reserve( value->Integer64List.nCount );
|
|
for ( int i = 0; i < value->Integer64List.nCount; ++i )
|
|
res << value->Integer64List.paList[ i ];
|
|
return res;
|
|
}
|
|
|
|
case OFTRealList:
|
|
{
|
|
QVariantList res;
|
|
res.reserve( value->RealList.nCount );
|
|
for ( int i = 0; i < value->RealList.nCount; ++i )
|
|
res << value->RealList.paList[ i ];
|
|
return res;
|
|
}
|
|
|
|
case OFTStringList:
|
|
case OFTWideStringList:
|
|
{
|
|
QVariantList res;
|
|
res.reserve( value->StringList.nCount );
|
|
for ( int i = 0; i < value->StringList.nCount; ++i )
|
|
res << QString::fromUtf8( value->StringList.paList[ i ] );
|
|
return res;
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
int QgsOgrUtils::OGRTZFlagFromQt( const QDateTime &datetime )
|
|
{
|
|
if ( datetime.timeSpec() == Qt::LocalTime )
|
|
return 1;
|
|
return 100 + datetime.offsetFromUtc() / ( 60 * 15 );
|
|
}
|
|
|
|
std::unique_ptr< OGRField > QgsOgrUtils::variantToOGRField( const QVariant &value, OGRFieldType type )
|
|
{
|
|
std::unique_ptr< OGRField > res = std::make_unique< OGRField >();
|
|
|
|
switch ( value.type() )
|
|
{
|
|
case QVariant::Invalid:
|
|
OGR_RawField_SetUnset( res.get() );
|
|
break;
|
|
case QVariant::Bool:
|
|
{
|
|
const int val = value.toBool() ? 1 : 0;
|
|
if ( type == OFTInteger )
|
|
res->Integer = val;
|
|
else if ( type == OFTInteger64 )
|
|
res->Integer64 = val;
|
|
else if ( type == OFTReal )
|
|
res->Real = val;
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for Bool" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::Int:
|
|
{
|
|
const int val = value.toInt();
|
|
if ( type == OFTInteger )
|
|
res->Integer = val;
|
|
else if ( type == OFTInteger64 )
|
|
res->Integer64 = val;
|
|
else if ( type == OFTReal )
|
|
res->Real = val;
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for Int" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::LongLong:
|
|
{
|
|
const qint64 val = value.toLongLong();
|
|
if ( type == OFTInteger )
|
|
{
|
|
if ( val <= std::numeric_limits<int>::max() &&
|
|
val >= std::numeric_limits<int>::min() )
|
|
{
|
|
res->Integer = static_cast<int>( val );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Value does not fit on Integer" );
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if ( type == OFTInteger64 )
|
|
res->Integer64 = val;
|
|
else if ( type == OFTReal )
|
|
{
|
|
res->Real = static_cast<double>( val );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for LongLong" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::Double:
|
|
{
|
|
double val = value.toDouble();
|
|
if ( type == OFTInteger )
|
|
{
|
|
if ( val <= std::numeric_limits<int>::max() &&
|
|
val >= std::numeric_limits<int>::min() )
|
|
{
|
|
res->Integer = static_cast<int>( val );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Value does not fit on Integer" );
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if ( type == OFTInteger64 )
|
|
{
|
|
if ( val <= static_cast<double>( std::numeric_limits<qint64>::max() ) &&
|
|
val >= static_cast<double>( std::numeric_limits<qint64>::min() ) )
|
|
{
|
|
res->Integer64 = static_cast<qint64>( val );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Value does not fit on Integer64" );
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if ( type == OFTReal )
|
|
{
|
|
res->Real = val;
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for LongLong" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::Char:
|
|
case QVariant::String:
|
|
{
|
|
if ( type == OFTString )
|
|
res->String = CPLStrdup( value.toString().toUtf8().constData() );
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for String" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::Date:
|
|
{
|
|
if ( type == OFTDate )
|
|
{
|
|
const QDate date = value.toDate();
|
|
res->Date.Day = date.day();
|
|
res->Date.Month = date.month();
|
|
res->Date.Year = static_cast<GInt16>( date.year() );
|
|
res->Date.TZFlag = 0;
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for Date" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::Time:
|
|
{
|
|
if ( type == OFTTime )
|
|
{
|
|
const QTime time = value.toTime();
|
|
res->Date.Hour = time.hour();
|
|
res->Date.Minute = time.minute();
|
|
res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
|
|
res->Date.TZFlag = 0;
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for Time" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
case QVariant::DateTime:
|
|
{
|
|
if ( type == OFTDateTime )
|
|
{
|
|
const QDateTime dt = value.toDateTime();
|
|
const QDate date = dt.date();
|
|
res->Date.Day = date.day();
|
|
res->Date.Month = date.month();
|
|
res->Date.Year = static_cast<GInt16>( date.year() );
|
|
const QTime time = dt.time();
|
|
res->Date.Hour = time.hour();
|
|
res->Date.Minute = time.minute();
|
|
res->Date.Second = static_cast<float>( time.second() + static_cast< double >( time.msec() ) / 1000 );
|
|
res->Date.TZFlag = OGRTZFlagFromQt( dt );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( "Unsupported output data type for DateTime" );
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
QgsDebugError( "Unhandled variant type in variantToOGRField" );
|
|
OGR_RawField_SetUnset( res.get() );
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
QgsFeature QgsOgrUtils::readOgrFeature( OGRFeatureH ogrFet, const QgsFields &fields, QTextCodec *encoding )
|
|
{
|
|
QgsFeature feature;
|
|
if ( !ogrFet )
|
|
{
|
|
feature.setValid( false );
|
|
return feature;
|
|
}
|
|
|
|
feature.setId( OGR_F_GetFID( ogrFet ) );
|
|
feature.setValid( true );
|
|
|
|
if ( !readOgrFeatureGeometry( ogrFet, feature ) )
|
|
{
|
|
feature.setValid( false );
|
|
}
|
|
|
|
if ( !readOgrFeatureAttributes( ogrFet, fields, feature, encoding ) )
|
|
{
|
|
feature.setValid( false );
|
|
}
|
|
|
|
return feature;
|
|
}
|
|
|
|
QgsFields QgsOgrUtils::readOgrFields( OGRFeatureH ogrFet, QTextCodec *encoding )
|
|
{
|
|
QgsFields fields;
|
|
|
|
if ( !ogrFet )
|
|
return fields;
|
|
|
|
int fieldCount = OGR_F_GetFieldCount( ogrFet );
|
|
for ( int i = 0; i < fieldCount; ++i )
|
|
{
|
|
OGRFieldDefnH fldDef = OGR_F_GetFieldDefnRef( ogrFet, i );
|
|
if ( !fldDef )
|
|
{
|
|
fields.append( QgsField() );
|
|
continue;
|
|
}
|
|
|
|
QString name = encoding ? encoding->toUnicode( OGR_Fld_GetNameRef( fldDef ) ) : QString::fromUtf8( OGR_Fld_GetNameRef( fldDef ) );
|
|
QVariant::Type varType;
|
|
switch ( OGR_Fld_GetType( fldDef ) )
|
|
{
|
|
case OFTInteger:
|
|
if ( OGR_Fld_GetSubType( fldDef ) == OFSTBoolean )
|
|
varType = QVariant::Bool;
|
|
else
|
|
varType = QVariant::Int;
|
|
break;
|
|
case OFTInteger64:
|
|
varType = QVariant::LongLong;
|
|
break;
|
|
case OFTReal:
|
|
varType = QVariant::Double;
|
|
break;
|
|
case OFTDate:
|
|
varType = QVariant::Date;
|
|
break;
|
|
case OFTTime:
|
|
varType = QVariant::Time;
|
|
break;
|
|
case OFTDateTime:
|
|
varType = QVariant::DateTime;
|
|
break;
|
|
case OFTString:
|
|
if ( OGR_Fld_GetSubType( fldDef ) == OFSTJSON )
|
|
varType = QVariant::Map;
|
|
else
|
|
varType = QVariant::String;
|
|
break;
|
|
default:
|
|
varType = QVariant::String; // other unsupported, leave it as a string
|
|
}
|
|
fields.append( QgsField( name, varType ) );
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
|
|
QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsFields &fields, int attIndex, QTextCodec *encoding, bool *ok )
|
|
{
|
|
if ( attIndex < 0 || attIndex >= fields.count() )
|
|
{
|
|
if ( ok )
|
|
*ok = false;
|
|
return QVariant();
|
|
}
|
|
|
|
const QgsField field = fields.at( attIndex );
|
|
return getOgrFeatureAttribute( ogrFet, field, attIndex, encoding, ok );
|
|
}
|
|
|
|
QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField &field, int attIndex, QTextCodec *encoding, bool *ok )
|
|
{
|
|
if ( !ogrFet || attIndex < 0 )
|
|
{
|
|
if ( ok )
|
|
*ok = false;
|
|
return QVariant();
|
|
}
|
|
|
|
OGRFieldDefnH fldDef = OGR_F_GetFieldDefnRef( ogrFet, attIndex );
|
|
|
|
if ( ! fldDef )
|
|
{
|
|
if ( ok )
|
|
*ok = false;
|
|
|
|
QgsDebugError( QStringLiteral( "ogrFet->GetFieldDefnRef(attindex) returns NULL" ) );
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant value;
|
|
|
|
if ( ok )
|
|
*ok = true;
|
|
|
|
if ( OGR_F_IsFieldSetAndNotNull( ogrFet, attIndex ) )
|
|
{
|
|
switch ( field.type() )
|
|
{
|
|
case QVariant::String:
|
|
{
|
|
if ( encoding )
|
|
value = QVariant( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
|
|
else
|
|
value = QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ) );
|
|
|
|
#ifdef Q_OS_WIN
|
|
// Fixes GH #41076 (empty strings shown as NULL), because we have checked before that it was NOT NULL
|
|
// Note: QVariant( QString( ) ).isNull( ) is still true on windows so we really need string literal :(
|
|
if ( value.isNull() )
|
|
value = QVariant( QStringLiteral( "" ) ); // skip-keyword-check
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
case QVariant::Int:
|
|
value = QVariant( OGR_F_GetFieldAsInteger( ogrFet, attIndex ) );
|
|
break;
|
|
case QVariant::Bool:
|
|
value = QVariant( bool( OGR_F_GetFieldAsInteger( ogrFet, attIndex ) ) );
|
|
break;
|
|
case QVariant::LongLong:
|
|
value = QVariant( OGR_F_GetFieldAsInteger64( ogrFet, attIndex ) );
|
|
break;
|
|
case QVariant::Double:
|
|
value = QVariant( OGR_F_GetFieldAsDouble( ogrFet, attIndex ) );
|
|
break;
|
|
case QVariant::Date:
|
|
case QVariant::DateTime:
|
|
case QVariant::Time:
|
|
{
|
|
int year, month, day, hour, minute, tzf;
|
|
float second;
|
|
float secondsPart = 0;
|
|
|
|
OGR_F_GetFieldAsDateTimeEx( ogrFet, attIndex, &year, &month, &day, &hour, &minute, &second, &tzf );
|
|
float millisecondPart = std::modf( second, &secondsPart );
|
|
|
|
if ( field.type() == QVariant::Date )
|
|
value = QDate( year, month, day );
|
|
else if ( field.type() == QVariant::Time )
|
|
value = QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) );
|
|
else
|
|
{
|
|
QDateTime dt = QDateTime( QDate( year, month, day ),
|
|
QTime( hour, minute, static_cast< int >( secondsPart ), static_cast< int >( std::round( 1000 * millisecondPart ) ) ) );
|
|
setQTTimeZoneFromOGRTZFlag( dt, tzf );
|
|
value = dt;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QVariant::ByteArray:
|
|
{
|
|
int size = 0;
|
|
const GByte *b = OGR_F_GetFieldAsBinary( ogrFet, attIndex, &size );
|
|
|
|
// QByteArray::fromRawData is funny. It doesn't take ownership of the data, so we have to explicitly call
|
|
// detach on it to force a copy which owns the data
|
|
QByteArray ba = QByteArray::fromRawData( reinterpret_cast<const char *>( b ), size );
|
|
ba.detach();
|
|
|
|
value = ba;
|
|
break;
|
|
}
|
|
|
|
case QVariant::StringList:
|
|
{
|
|
QStringList list;
|
|
char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
|
|
const int count = CSLCount( lst );
|
|
if ( count > 0 )
|
|
{
|
|
list.reserve( count );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( encoding )
|
|
list << encoding->toUnicode( lst[i] );
|
|
else
|
|
list << QString::fromUtf8( lst[i] );
|
|
}
|
|
}
|
|
value = list;
|
|
break;
|
|
}
|
|
|
|
case QVariant::List:
|
|
{
|
|
switch ( field.subType() )
|
|
{
|
|
case QVariant::String:
|
|
{
|
|
QStringList list;
|
|
char **lst = OGR_F_GetFieldAsStringList( ogrFet, attIndex );
|
|
const int count = CSLCount( lst );
|
|
if ( count > 0 )
|
|
{
|
|
list.reserve( count );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( encoding )
|
|
list << encoding->toUnicode( lst[i] );
|
|
else
|
|
list << QString::fromUtf8( lst[i] );
|
|
}
|
|
}
|
|
value = list;
|
|
break;
|
|
}
|
|
|
|
case QVariant::Int:
|
|
{
|
|
QVariantList list;
|
|
int count = 0;
|
|
const int *lst = OGR_F_GetFieldAsIntegerList( ogrFet, attIndex, &count );
|
|
if ( count > 0 )
|
|
{
|
|
list.reserve( count );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
list << lst[i];
|
|
}
|
|
}
|
|
value = list;
|
|
break;
|
|
}
|
|
|
|
case QVariant::Double:
|
|
{
|
|
QVariantList list;
|
|
int count = 0;
|
|
const double *lst = OGR_F_GetFieldAsDoubleList( ogrFet, attIndex, &count );
|
|
if ( count > 0 )
|
|
{
|
|
list.reserve( count );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
list << lst[i];
|
|
}
|
|
}
|
|
value = list;
|
|
break;
|
|
}
|
|
|
|
case QVariant::LongLong:
|
|
{
|
|
QVariantList list;
|
|
int count = 0;
|
|
const long long *lst = OGR_F_GetFieldAsInteger64List( ogrFet, attIndex, &count );
|
|
if ( count > 0 )
|
|
{
|
|
list.reserve( count );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
list << lst[i];
|
|
}
|
|
}
|
|
value = list;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
|
|
if ( ok )
|
|
*ok = false;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case QVariant::Map:
|
|
{
|
|
//it has to be JSON
|
|
//it's null if no json format
|
|
if ( encoding )
|
|
value = QJsonDocument::fromJson( encoding->toUnicode( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant();
|
|
else
|
|
value = QJsonDocument::fromJson( QString::fromUtf8( OGR_F_GetFieldAsString( ogrFet, attIndex ) ).toUtf8() ).toVariant();
|
|
break;
|
|
}
|
|
default:
|
|
Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
|
|
if ( ok )
|
|
*ok = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
value = QVariant( field.type() );
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool QgsOgrUtils::readOgrFeatureAttributes( OGRFeatureH ogrFet, const QgsFields &fields, QgsFeature &feature, QTextCodec *encoding )
|
|
{
|
|
// read all attributes
|
|
feature.initAttributes( fields.count() );
|
|
feature.setFields( fields );
|
|
|
|
if ( !ogrFet )
|
|
return false;
|
|
|
|
bool ok = false;
|
|
for ( int idx = 0; idx < fields.count(); ++idx )
|
|
{
|
|
QVariant value = getOgrFeatureAttribute( ogrFet, fields, idx, encoding, &ok );
|
|
if ( ok )
|
|
{
|
|
feature.setAttribute( idx, value );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QgsOgrUtils::readOgrFeatureGeometry( OGRFeatureH ogrFet, QgsFeature &feature )
|
|
{
|
|
if ( !ogrFet )
|
|
return false;
|
|
|
|
OGRGeometryH geom = OGR_F_GetGeometryRef( ogrFet );
|
|
if ( !geom )
|
|
feature.clearGeometry();
|
|
else
|
|
feature.setGeometry( ogrGeometryToQgsGeometry( geom ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr< QgsPoint > ogrGeometryToQgsPoint( OGRGeometryH geom )
|
|
{
|
|
Qgis::WkbType wkbType = QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGR_G_GetGeometryType( geom ) );
|
|
|
|
double x, y, z, m;
|
|
OGR_G_GetPointZM( geom, 0, &x, &y, &z, &m );
|
|
return std::make_unique< QgsPoint >( wkbType, x, y, z, m );
|
|
}
|
|
|
|
std::unique_ptr< QgsMultiPoint > ogrGeometryToQgsMultiPoint( OGRGeometryH geom )
|
|
{
|
|
std::unique_ptr< QgsMultiPoint > mp = std::make_unique< QgsMultiPoint >();
|
|
|
|
const int count = OGR_G_GetGeometryCount( geom );
|
|
mp->reserve( count );
|
|
for ( int i = 0; i < count; ++i )
|
|
{
|
|
mp->addGeometry( ogrGeometryToQgsPoint( OGR_G_GetGeometryRef( geom, i ) ).release() );
|
|
}
|
|
|
|
return mp;
|
|
}
|
|
|
|
std::unique_ptr< QgsLineString > ogrGeometryToQgsLineString( OGRGeometryH geom )
|
|
{
|
|
Qgis::WkbType wkbType = QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGR_G_GetGeometryType( geom ) );
|
|
|
|
int count = OGR_G_GetPointCount( geom );
|
|
QVector< double > x( count );
|
|
QVector< double > y( count );
|
|
QVector< double > z;
|
|
double *pz = nullptr;
|
|
if ( QgsWkbTypes::hasZ( wkbType ) )
|
|
{
|
|
z.resize( count );
|
|
pz = z.data();
|
|
}
|
|
double *pm = nullptr;
|
|
QVector< double > m;
|
|
if ( QgsWkbTypes::hasM( wkbType ) )
|
|
{
|
|
m.resize( count );
|
|
pm = m.data();
|
|
}
|
|
OGR_G_GetPointsZM( geom, x.data(), sizeof( double ), y.data(), sizeof( double ), pz, sizeof( double ), pm, sizeof( double ) );
|
|
|
|
return std::make_unique< QgsLineString>( x, y, z, m, wkbType == Qgis::WkbType::LineString25D );
|
|
}
|
|
|
|
std::unique_ptr< QgsMultiLineString > ogrGeometryToQgsMultiLineString( OGRGeometryH geom )
|
|
{
|
|
std::unique_ptr< QgsMultiLineString > mp = std::make_unique< QgsMultiLineString >();
|
|
|
|
const int count = OGR_G_GetGeometryCount( geom );
|
|
mp->reserve( count );
|
|
for ( int i = 0; i < count; ++i )
|
|
{
|
|
mp->addGeometry( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, i ) ).release() );
|
|
}
|
|
|
|
return mp;
|
|
}
|
|
|
|
std::unique_ptr< QgsPolygon > ogrGeometryToQgsPolygon( OGRGeometryH geom )
|
|
{
|
|
std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
|
|
|
|
const int count = OGR_G_GetGeometryCount( geom );
|
|
if ( count >= 1 )
|
|
{
|
|
polygon->setExteriorRing( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, 0 ) ).release() );
|
|
}
|
|
|
|
for ( int i = 1; i < count; ++i )
|
|
{
|
|
polygon->addInteriorRing( ogrGeometryToQgsLineString( OGR_G_GetGeometryRef( geom, i ) ).release() );
|
|
}
|
|
|
|
return polygon;
|
|
}
|
|
|
|
std::unique_ptr< QgsMultiPolygon > ogrGeometryToQgsMultiPolygon( OGRGeometryH geom )
|
|
{
|
|
std::unique_ptr< QgsMultiPolygon > polygon = std::make_unique< QgsMultiPolygon >();
|
|
|
|
const int count = OGR_G_GetGeometryCount( geom );
|
|
polygon->reserve( count );
|
|
for ( int i = 0; i < count; ++i )
|
|
{
|
|
polygon->addGeometry( ogrGeometryToQgsPolygon( OGR_G_GetGeometryRef( geom, i ) ).release() );
|
|
}
|
|
|
|
return polygon;
|
|
}
|
|
|
|
Qgis::WkbType QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGRwkbGeometryType ogrGeomType )
|
|
{
|
|
switch ( ogrGeomType )
|
|
{
|
|
case wkbUnknown: return Qgis::WkbType::Unknown;
|
|
case wkbPoint: return Qgis::WkbType::Point;
|
|
case wkbLineString: return Qgis::WkbType::LineString;
|
|
case wkbPolygon: return Qgis::WkbType::Polygon;
|
|
case wkbMultiPoint: return Qgis::WkbType::MultiPoint;
|
|
case wkbMultiLineString: return Qgis::WkbType::MultiLineString;
|
|
case wkbMultiPolygon: return Qgis::WkbType::MultiPolygon;
|
|
case wkbGeometryCollection: return Qgis::WkbType::GeometryCollection;
|
|
case wkbCircularString: return Qgis::WkbType::CircularString;
|
|
case wkbCompoundCurve: return Qgis::WkbType::CompoundCurve;
|
|
case wkbCurvePolygon: return Qgis::WkbType::CurvePolygon;
|
|
case wkbMultiCurve: return Qgis::WkbType::MultiCurve;
|
|
case wkbMultiSurface: return Qgis::WkbType::MultiSurface;
|
|
case wkbCurve: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbSurface: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbPolyhedralSurface: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTIN: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTriangle: return Qgis::WkbType::Triangle;
|
|
|
|
case wkbNone: return Qgis::WkbType::NoGeometry;
|
|
case wkbLinearRing: return Qgis::WkbType::LineString; // approximate match
|
|
|
|
case wkbCircularStringZ: return Qgis::WkbType::CircularStringZ;
|
|
case wkbCompoundCurveZ: return Qgis::WkbType::CompoundCurveZ;
|
|
case wkbCurvePolygonZ: return Qgis::WkbType::CurvePolygonZ;
|
|
case wkbMultiCurveZ: return Qgis::WkbType::MultiCurveZ;
|
|
case wkbMultiSurfaceZ: return Qgis::WkbType::MultiSurfaceZ;
|
|
case wkbCurveZ: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbSurfaceZ: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbPolyhedralSurfaceZ: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTINZ: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTriangleZ: return Qgis::WkbType::TriangleZ;
|
|
|
|
case wkbPointM: return Qgis::WkbType::PointM;
|
|
case wkbLineStringM: return Qgis::WkbType::LineStringM;
|
|
case wkbPolygonM: return Qgis::WkbType::PolygonM;
|
|
case wkbMultiPointM: return Qgis::WkbType::MultiPointM;
|
|
case wkbMultiLineStringM: return Qgis::WkbType::MultiLineStringM;
|
|
case wkbMultiPolygonM: return Qgis::WkbType::MultiPolygonM;
|
|
case wkbGeometryCollectionM: return Qgis::WkbType::GeometryCollectionM;
|
|
case wkbCircularStringM: return Qgis::WkbType::CircularStringM;
|
|
case wkbCompoundCurveM: return Qgis::WkbType::CompoundCurveM;
|
|
case wkbCurvePolygonM: return Qgis::WkbType::CurvePolygonM;
|
|
case wkbMultiCurveM: return Qgis::WkbType::MultiCurveM;
|
|
case wkbMultiSurfaceM: return Qgis::WkbType::MultiSurfaceM;
|
|
case wkbCurveM: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbSurfaceM: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbPolyhedralSurfaceM: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTINM: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTriangleM: return Qgis::WkbType::TriangleM;
|
|
|
|
case wkbPointZM: return Qgis::WkbType::PointZM;
|
|
case wkbLineStringZM: return Qgis::WkbType::LineStringZM;
|
|
case wkbPolygonZM: return Qgis::WkbType::PolygonZM;
|
|
case wkbMultiPointZM: return Qgis::WkbType::MultiPointZM;
|
|
case wkbMultiLineStringZM: return Qgis::WkbType::MultiLineStringZM;
|
|
case wkbMultiPolygonZM: return Qgis::WkbType::MultiPolygonZM;
|
|
case wkbGeometryCollectionZM: return Qgis::WkbType::GeometryCollectionZM;
|
|
case wkbCircularStringZM: return Qgis::WkbType::CircularStringZM;
|
|
case wkbCompoundCurveZM: return Qgis::WkbType::CompoundCurveZM;
|
|
case wkbCurvePolygonZM: return Qgis::WkbType::CurvePolygonZM;
|
|
case wkbMultiCurveZM: return Qgis::WkbType::MultiCurveZM;
|
|
case wkbMultiSurfaceZM: return Qgis::WkbType::MultiSurfaceZM;
|
|
case wkbCurveZM: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbSurfaceZM: return Qgis::WkbType::Unknown; // not an actual concrete type
|
|
case wkbPolyhedralSurfaceZM: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTINZM: return Qgis::WkbType::Unknown; // no actual matching
|
|
case wkbTriangleZM: return Qgis::WkbType::TriangleZM;
|
|
|
|
case wkbPoint25D: return Qgis::WkbType::PointZ;
|
|
case wkbLineString25D: return Qgis::WkbType::LineStringZ;
|
|
case wkbPolygon25D: return Qgis::WkbType::PolygonZ;
|
|
case wkbMultiPoint25D: return Qgis::WkbType::MultiPointZ;
|
|
case wkbMultiLineString25D: return Qgis::WkbType::MultiLineStringZ;
|
|
case wkbMultiPolygon25D: return Qgis::WkbType::MultiPolygonZ;
|
|
case wkbGeometryCollection25D: return Qgis::WkbType::GeometryCollectionZ;
|
|
}
|
|
|
|
// should not reach that point normally
|
|
return Qgis::WkbType::Unknown;
|
|
}
|
|
|
|
QgsGeometry QgsOgrUtils::ogrGeometryToQgsGeometry( OGRGeometryH geom )
|
|
{
|
|
if ( !geom )
|
|
return QgsGeometry();
|
|
|
|
const auto ogrGeomType = OGR_G_GetGeometryType( geom );
|
|
Qgis::WkbType wkbType = ogrGeometryTypeToQgsWkbType( ogrGeomType );
|
|
|
|
// optimised case for some geometry classes, avoiding wkb conversion on OGR/QGIS sides
|
|
// TODO - extend to other classes!
|
|
switch ( QgsWkbTypes::flatType( wkbType ) )
|
|
{
|
|
case Qgis::WkbType::Point:
|
|
{
|
|
return QgsGeometry( ogrGeometryToQgsPoint( geom ) );
|
|
}
|
|
|
|
case Qgis::WkbType::MultiPoint:
|
|
{
|
|
return QgsGeometry( ogrGeometryToQgsMultiPoint( geom ) );
|
|
}
|
|
|
|
case Qgis::WkbType::LineString:
|
|
{
|
|
return QgsGeometry( ogrGeometryToQgsLineString( geom ) );
|
|
}
|
|
|
|
case Qgis::WkbType::MultiLineString:
|
|
{
|
|
return QgsGeometry( ogrGeometryToQgsMultiLineString( geom ) );
|
|
}
|
|
|
|
case Qgis::WkbType::Polygon:
|
|
{
|
|
return QgsGeometry( ogrGeometryToQgsPolygon( geom ) );
|
|
}
|
|
|
|
case Qgis::WkbType::MultiPolygon:
|
|
{
|
|
return QgsGeometry( ogrGeometryToQgsMultiPolygon( geom ) );
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Fallback to inefficient WKB conversions
|
|
|
|
if ( wkbFlatten( wkbType ) == wkbGeometryCollection )
|
|
{
|
|
// Shapefile MultiPatch can be reported as GeometryCollectionZ of TINZ
|
|
if ( OGR_G_GetGeometryCount( geom ) >= 1 &&
|
|
wkbFlatten( OGR_G_GetGeometryType( OGR_G_GetGeometryRef( geom, 0 ) ) ) == wkbTIN )
|
|
{
|
|
auto newGeom = OGR_G_ForceToMultiPolygon( OGR_G_Clone( geom ) );
|
|
auto ret = ogrGeometryToQgsGeometry( newGeom );
|
|
OGR_G_DestroyGeometry( newGeom );
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// get the wkb representation
|
|
int memorySize = OGR_G_WkbSize( geom );
|
|
unsigned char *wkb = new unsigned char[memorySize];
|
|
OGR_G_ExportToWkb( geom, static_cast<OGRwkbByteOrder>( QgsApplication::endian() ), wkb );
|
|
|
|
// Read original geometry type
|
|
uint32_t origGeomType;
|
|
memcpy( &origGeomType, wkb + 1, sizeof( uint32_t ) );
|
|
bool hasZ = ( origGeomType >= 1000 && origGeomType < 2000 ) || ( origGeomType >= 3000 && origGeomType < 4000 );
|
|
bool hasM = ( origGeomType >= 2000 && origGeomType < 3000 ) || ( origGeomType >= 3000 && origGeomType < 4000 );
|
|
|
|
// PolyhedralSurface and TINs are not supported, map them to multipolygons...
|
|
if ( origGeomType % 1000 == 16 ) // is TIN, TINZ, TINM or TINZM
|
|
{
|
|
// TIN has the same wkb layout as a multipolygon, just need to overwrite the geom types...
|
|
int nDims = 2 + hasZ + hasM;
|
|
uint32_t newMultiType = static_cast<uint32_t>( QgsWkbTypes::zmType( Qgis::WkbType::MultiPolygon, hasZ, hasM ) );
|
|
uint32_t newSingleType = static_cast<uint32_t>( QgsWkbTypes::zmType( Qgis::WkbType::Polygon, hasZ, hasM ) );
|
|
unsigned char *wkbptr = wkb;
|
|
|
|
// Endianness
|
|
wkbptr += 1;
|
|
|
|
// Overwrite geom type
|
|
memcpy( wkbptr, &newMultiType, sizeof( uint32_t ) );
|
|
wkbptr += 4;
|
|
|
|
// Geom count
|
|
uint32_t numGeoms;
|
|
memcpy( &numGeoms, wkb + 5, sizeof( uint32_t ) );
|
|
wkbptr += 4;
|
|
|
|
// For each part, overwrite the geometry type to polygon (Z|M)
|
|
for ( uint32_t i = 0; i < numGeoms; ++i )
|
|
{
|
|
// Endianness
|
|
wkbptr += 1;
|
|
|
|
// Overwrite geom type
|
|
memcpy( wkbptr, &newSingleType, sizeof( uint32_t ) );
|
|
wkbptr += sizeof( uint32_t );
|
|
|
|
// skip coordinates
|
|
uint32_t nRings;
|
|
memcpy( &nRings, wkbptr, sizeof( uint32_t ) );
|
|
wkbptr += sizeof( uint32_t );
|
|
|
|
for ( uint32_t j = 0; j < nRings; ++j )
|
|
{
|
|
uint32_t nPoints;
|
|
memcpy( &nPoints, wkbptr, sizeof( uint32_t ) );
|
|
wkbptr += sizeof( uint32_t ) + sizeof( double ) * nDims * nPoints;
|
|
}
|
|
}
|
|
}
|
|
else if ( origGeomType % 1000 == 15 ) // PolyhedralSurface, PolyhedralSurfaceZ, PolyhedralSurfaceM or PolyhedralSurfaceZM
|
|
{
|
|
// PolyhedralSurface has the same wkb layout as a MultiPolygon, just need to overwrite the geom type...
|
|
uint32_t newType = static_cast<uint32_t>( QgsWkbTypes::zmType( Qgis::WkbType::MultiPolygon, hasZ, hasM ) );
|
|
// Overwrite geom type
|
|
memcpy( wkb + 1, &newType, sizeof( uint32_t ) );
|
|
}
|
|
|
|
QgsGeometry g;
|
|
g.fromWkb( wkb, memorySize );
|
|
return g;
|
|
}
|
|
|
|
QgsFeatureList QgsOgrUtils::stringToFeatureList( const QString &string, const QgsFields &fields, QTextCodec *encoding )
|
|
{
|
|
QgsFeatureList features;
|
|
if ( string.isEmpty() )
|
|
return features;
|
|
|
|
QString randomFileName = QStringLiteral( "/vsimem/%1" ).arg( QUuid::createUuid().toString() );
|
|
|
|
// create memory file system object from string buffer
|
|
QByteArray ba = string.toUtf8();
|
|
VSIFCloseL( VSIFileFromMemBuffer( randomFileName.toUtf8().constData(), reinterpret_cast< GByte * >( ba.data() ),
|
|
static_cast< vsi_l_offset >( ba.size() ), FALSE ) );
|
|
|
|
gdal::ogr_datasource_unique_ptr hDS( OGROpen( randomFileName.toUtf8().constData(), false, nullptr ) );
|
|
if ( !hDS )
|
|
{
|
|
VSIUnlink( randomFileName.toUtf8().constData() );
|
|
return features;
|
|
}
|
|
|
|
OGRLayerH ogrLayer = OGR_DS_GetLayer( hDS.get(), 0 );
|
|
if ( !ogrLayer )
|
|
{
|
|
hDS.reset();
|
|
VSIUnlink( randomFileName.toUtf8().constData() );
|
|
return features;
|
|
}
|
|
|
|
gdal::ogr_feature_unique_ptr oFeat;
|
|
while ( oFeat.reset( OGR_L_GetNextFeature( ogrLayer ) ), oFeat )
|
|
{
|
|
QgsFeature feat = readOgrFeature( oFeat.get(), fields, encoding );
|
|
if ( feat.isValid() )
|
|
features << feat;
|
|
}
|
|
|
|
hDS.reset();
|
|
VSIUnlink( randomFileName.toUtf8().constData() );
|
|
|
|
return features;
|
|
}
|
|
|
|
QgsFields QgsOgrUtils::stringToFields( const QString &string, QTextCodec *encoding )
|
|
{
|
|
QgsFields fields;
|
|
if ( string.isEmpty() )
|
|
return fields;
|
|
|
|
QString randomFileName = QStringLiteral( "/vsimem/%1" ).arg( QUuid::createUuid().toString() );
|
|
|
|
// create memory file system object from buffer
|
|
QByteArray ba = string.toUtf8();
|
|
VSIFCloseL( VSIFileFromMemBuffer( randomFileName.toUtf8().constData(), reinterpret_cast< GByte * >( ba.data() ),
|
|
static_cast< vsi_l_offset >( ba.size() ), FALSE ) );
|
|
|
|
gdal::ogr_datasource_unique_ptr hDS( OGROpen( randomFileName.toUtf8().constData(), false, nullptr ) );
|
|
if ( !hDS )
|
|
{
|
|
VSIUnlink( randomFileName.toUtf8().constData() );
|
|
return fields;
|
|
}
|
|
|
|
OGRLayerH ogrLayer = OGR_DS_GetLayer( hDS.get(), 0 );
|
|
if ( !ogrLayer )
|
|
{
|
|
hDS.reset();
|
|
VSIUnlink( randomFileName.toUtf8().constData() );
|
|
return fields;
|
|
}
|
|
|
|
gdal::ogr_feature_unique_ptr oFeat;
|
|
//read in the first feature only
|
|
if ( oFeat.reset( OGR_L_GetNextFeature( ogrLayer ) ), oFeat )
|
|
{
|
|
fields = readOgrFields( oFeat.get(), encoding );
|
|
}
|
|
|
|
hDS.reset();
|
|
VSIUnlink( randomFileName.toUtf8().constData() );
|
|
return fields;
|
|
}
|
|
|
|
QStringList QgsOgrUtils::cStringListToQStringList( char **stringList )
|
|
{
|
|
if ( !stringList )
|
|
return {};
|
|
|
|
QStringList strings;
|
|
// presume null terminated string list
|
|
for ( qgssize i = 0; stringList[i]; ++i )
|
|
{
|
|
strings.append( QString::fromUtf8( stringList[i] ) );
|
|
}
|
|
|
|
return strings;
|
|
}
|
|
|
|
QString QgsOgrUtils::OGRSpatialReferenceToWkt( OGRSpatialReferenceH srs )
|
|
{
|
|
if ( !srs )
|
|
return QString();
|
|
|
|
char *pszWkt = nullptr;
|
|
const QByteArray multiLineOption = QStringLiteral( "MULTILINE=NO" ).toLocal8Bit();
|
|
const QByteArray formatOption = QStringLiteral( "FORMAT=WKT2" ).toLocal8Bit();
|
|
const char *const options[] = {multiLineOption.constData(), formatOption.constData(), nullptr};
|
|
OSRExportToWktEx( srs, &pszWkt, options );
|
|
|
|
const QString res( pszWkt );
|
|
CPLFree( pszWkt );
|
|
return res;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsOgrUtils::OGRSpatialReferenceToCrs( OGRSpatialReferenceH srs )
|
|
{
|
|
const QString wkt = OGRSpatialReferenceToWkt( srs );
|
|
if ( wkt.isEmpty() )
|
|
return QgsCoordinateReferenceSystem();
|
|
|
|
const char *authorityName = OSRGetAuthorityName( srs, nullptr );
|
|
const char *authorityCode = OSRGetAuthorityCode( srs, nullptr );
|
|
QgsCoordinateReferenceSystem res;
|
|
if ( authorityName && authorityCode )
|
|
{
|
|
QString authId = QString( authorityName ) + ':' + QString( authorityCode );
|
|
OGRSpatialReferenceH ogrSrsTmp = OSRNewSpatialReference( nullptr );
|
|
// Check that the CRS build from authId and the input one are the "same".
|
|
if ( OSRSetFromUserInput( ogrSrsTmp, authId.toUtf8().constData() ) != OGRERR_NONE &&
|
|
OSRIsSame( srs, ogrSrsTmp ) )
|
|
{
|
|
res = QgsCoordinateReferenceSystem();
|
|
res.createFromUserInput( authId );
|
|
}
|
|
OSRDestroySpatialReference( ogrSrsTmp );
|
|
}
|
|
if ( !res.isValid() )
|
|
res = QgsCoordinateReferenceSystem::fromWkt( wkt );
|
|
|
|
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,4,0)
|
|
const double coordinateEpoch = OSRGetCoordinateEpoch( srs );
|
|
if ( coordinateEpoch > 0 )
|
|
res.setCoordinateEpoch( coordinateEpoch );
|
|
#endif
|
|
return res;
|
|
}
|
|
|
|
OGRSpatialReferenceH QgsOgrUtils::crsToOGRSpatialReference( const QgsCoordinateReferenceSystem &crs )
|
|
{
|
|
if ( crs.isValid() )
|
|
{
|
|
OGRSpatialReferenceH ogrSrs = nullptr;
|
|
|
|
// First try instantiating the CRS from its authId. This will give a
|
|
// more complete representation of the CRS for GDAL. In particular it might
|
|
// help a few drivers to get the datum code, that would be missing in WKT-2.
|
|
// See https://github.com/OSGeo/gdal/pull/5218
|
|
const QString authId = crs.authid();
|
|
const QString srsWkt = crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL );
|
|
if ( !authId.isEmpty() )
|
|
{
|
|
ogrSrs = OSRNewSpatialReference( nullptr );
|
|
if ( OSRSetFromUserInput( ogrSrs, authId.toUtf8().constData() ) == OGRERR_NONE )
|
|
{
|
|
// Check that the CRS build from WKT and authId are the "same".
|
|
OGRSpatialReferenceH ogrSrsFromWkt = OSRNewSpatialReference( srsWkt.toUtf8().constData() );
|
|
if ( ogrSrsFromWkt )
|
|
{
|
|
if ( !OSRIsSame( ogrSrs, ogrSrsFromWkt ) )
|
|
{
|
|
OSRDestroySpatialReference( ogrSrs );
|
|
ogrSrs = ogrSrsFromWkt;
|
|
}
|
|
else
|
|
{
|
|
OSRDestroySpatialReference( ogrSrsFromWkt );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSRDestroySpatialReference( ogrSrs );
|
|
ogrSrs = nullptr;
|
|
}
|
|
}
|
|
if ( !ogrSrs )
|
|
{
|
|
ogrSrs = OSRNewSpatialReference( srsWkt.toUtf8().constData() );
|
|
}
|
|
if ( ogrSrs )
|
|
{
|
|
OSRSetAxisMappingStrategy( ogrSrs, OAMS_TRADITIONAL_GIS_ORDER );
|
|
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,4,0)
|
|
if ( !std::isnan( crs.coordinateEpoch() ) )
|
|
{
|
|
OSRSetCoordinateEpoch( ogrSrs, crs.coordinateEpoch() );
|
|
}
|
|
#endif
|
|
return ogrSrs;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QString QgsOgrUtils::readShapefileEncoding( const QString &path )
|
|
{
|
|
const QString cpgEncoding = readShapefileEncodingFromCpg( path );
|
|
if ( !cpgEncoding.isEmpty() )
|
|
return cpgEncoding;
|
|
|
|
return readShapefileEncodingFromLdid( path );
|
|
}
|
|
|
|
QString QgsOgrUtils::readShapefileEncodingFromCpg( const QString &path )
|
|
{
|
|
QString errCause;
|
|
QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( path, false, QStringList(), 0, errCause, false );
|
|
return layer ? layer->GetMetadataItem( QStringLiteral( "ENCODING_FROM_CPG" ), QStringLiteral( "SHAPEFILE" ) ) : QString();
|
|
}
|
|
|
|
QString QgsOgrUtils::readShapefileEncodingFromLdid( const QString &path )
|
|
{
|
|
QString errCause;
|
|
QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( path, false, QStringList(), 0, errCause, false );
|
|
return layer ? layer->GetMetadataItem( QStringLiteral( "ENCODING_FROM_LDID" ), QStringLiteral( "SHAPEFILE" ) ) : QString();
|
|
}
|
|
|
|
QVariantMap QgsOgrUtils::parseStyleString( const QString &string )
|
|
{
|
|
QVariantMap styles;
|
|
|
|
char **papszStyleString = CSLTokenizeString2( string.toUtf8().constData(), ";",
|
|
CSLT_HONOURSTRINGS
|
|
| CSLT_PRESERVEQUOTES
|
|
| CSLT_PRESERVEESCAPES );
|
|
for ( int i = 0; papszStyleString[i] != nullptr; ++i )
|
|
{
|
|
// style string format is:
|
|
// <tool_name>([<tool_param>[,<tool_param>[,...]]])
|
|
|
|
// first extract tool name
|
|
const thread_local QRegularExpression sToolPartRx( QStringLiteral( "^(.*?)\\((.*)\\)$" ) );
|
|
const QString stylePart( papszStyleString[i] );
|
|
const QRegularExpressionMatch match = sToolPartRx.match( stylePart );
|
|
if ( !match.hasMatch() )
|
|
continue;
|
|
|
|
const QString tool = match.captured( 1 );
|
|
const QString params = match.captured( 2 );
|
|
|
|
char **papszTokens = CSLTokenizeString2( params.toUtf8().constData(), ",", CSLT_HONOURSTRINGS
|
|
| CSLT_PRESERVEESCAPES );
|
|
|
|
QVariantMap toolParts;
|
|
const thread_local QRegularExpression sToolParamRx( QStringLiteral( "^(.*?):(.*)$" ) );
|
|
for ( int j = 0; papszTokens[j] != nullptr; ++j )
|
|
{
|
|
const QString toolPart( papszTokens[j] );
|
|
const QRegularExpressionMatch toolMatch = sToolParamRx.match( toolPart );
|
|
if ( !match.hasMatch() )
|
|
continue;
|
|
|
|
// note we always convert the keys to lowercase, just to be safe...
|
|
toolParts.insert( toolMatch.captured( 1 ).toLower(), toolMatch.captured( 2 ) );
|
|
}
|
|
CSLDestroy( papszTokens );
|
|
|
|
// note we always convert the keys to lowercase, just to be safe...
|
|
styles.insert( tool.toLower(), toolParts );
|
|
}
|
|
CSLDestroy( papszStyleString );
|
|
return styles;
|
|
}
|
|
|
|
std::unique_ptr<QgsSymbol> QgsOgrUtils::symbolFromStyleString( const QString &string, Qgis::SymbolType type )
|
|
{
|
|
const QVariantMap styles = parseStyleString( string );
|
|
|
|
auto convertSize = []( const QString & size, double & value, Qgis::RenderUnit & unit )->bool
|
|
{
|
|
const thread_local QRegularExpression sUnitRx = QRegularExpression( QStringLiteral( "^([\\d\\.]+)(g|px|pt|mm|cm|in)$" ) );
|
|
const QRegularExpressionMatch match = sUnitRx.match( size );
|
|
if ( match.hasMatch() )
|
|
{
|
|
value = match.captured( 1 ).toDouble();
|
|
const QString unitString = match.captured( 2 );
|
|
if ( unitString.compare( QLatin1String( "px" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
// pixels are a poor unit choice for QGIS -- they render badly in hidpi layouts. Convert to points instead, using
|
|
// a 96 dpi conversion
|
|
static constexpr double PT_TO_INCHES_FACTOR = 1 / 72.0;
|
|
static constexpr double PX_TO_PT_FACTOR = 1 / ( 96.0 * PT_TO_INCHES_FACTOR );
|
|
unit = Qgis::RenderUnit::Points;
|
|
value *= PX_TO_PT_FACTOR;
|
|
return true;
|
|
}
|
|
else if ( unitString.compare( QLatin1String( "pt" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
unit = Qgis::RenderUnit::Points;
|
|
return true;
|
|
}
|
|
else if ( unitString.compare( QLatin1String( "mm" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
unit = Qgis::RenderUnit::Millimeters;
|
|
return true;
|
|
}
|
|
else if ( unitString.compare( QLatin1String( "cm" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
value *= 10;
|
|
unit = Qgis::RenderUnit::Millimeters;
|
|
return true;
|
|
}
|
|
else if ( unitString.compare( QLatin1String( "in" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
unit = Qgis::RenderUnit::Inches;
|
|
return true;
|
|
}
|
|
else if ( unitString.compare( QLatin1String( "g" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
unit = Qgis::RenderUnit::MapUnits;
|
|
return true;
|
|
}
|
|
QgsDebugError( QStringLiteral( "Unknown unit %1" ).arg( unitString ) );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugError( QStringLiteral( "Could not parse style size %1" ).arg( size ) );
|
|
}
|
|
return false;
|
|
};
|
|
|
|
auto convertColor = []( const QString & string ) -> QColor
|
|
{
|
|
if ( string.isEmpty() )
|
|
return QColor();
|
|
|
|
const thread_local QRegularExpression sColorWithAlphaRx = QRegularExpression( QStringLiteral( "^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})$" ) );
|
|
const QRegularExpressionMatch match = sColorWithAlphaRx.match( string );
|
|
if ( match.hasMatch() )
|
|
{
|
|
// need to convert #RRGGBBAA to #AARRGGBB for QColor
|
|
return QColor( QStringLiteral( "#%1%2" ).arg( match.captured( 2 ), match.captured( 1 ) ) );
|
|
}
|
|
else
|
|
{
|
|
return QColor( string );
|
|
}
|
|
};
|
|
|
|
auto convertPen = [&convertColor, &convertSize, string]( const QVariantMap & lineStyle ) -> std::unique_ptr< QgsSymbol >
|
|
{
|
|
QColor color = convertColor( lineStyle.value( QStringLiteral( "c" ), QStringLiteral( "#000000" ) ).toString() );
|
|
|
|
double lineWidth = DEFAULT_SIMPLELINE_WIDTH;
|
|
Qgis::RenderUnit lineWidthUnit = Qgis::RenderUnit::Millimeters;
|
|
convertSize( lineStyle.value( QStringLiteral( "w" ) ).toString(), lineWidth, lineWidthUnit );
|
|
|
|
// if the pen is a mapinfo pen, use dedicated converter for more accurate results
|
|
const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-pen-(\\d+)" ) );
|
|
const QRegularExpressionMatch match = sMapInfoId.match( string );
|
|
if ( match.hasMatch() )
|
|
{
|
|
const int penId = match.captured( 1 ).toInt();
|
|
QgsMapInfoSymbolConversionContext context;
|
|
std::unique_ptr<QgsSymbol> res( QgsMapInfoSymbolConverter::convertLineSymbol( penId, context, color, lineWidth, lineWidthUnit ) );
|
|
if ( res )
|
|
return res;
|
|
}
|
|
|
|
std::unique_ptr< QgsSimpleLineSymbolLayer > simpleLine = std::make_unique< QgsSimpleLineSymbolLayer >( color, lineWidth );
|
|
simpleLine->setWidthUnit( lineWidthUnit );
|
|
|
|
// pattern
|
|
const QString pattern = lineStyle.value( QStringLiteral( "p" ) ).toString();
|
|
if ( !pattern.isEmpty() )
|
|
{
|
|
const thread_local QRegularExpression sPatternUnitRx = QRegularExpression( QStringLiteral( "^([\\d\\.\\s]+)(g|px|pt|mm|cm|in)$" ) );
|
|
const QRegularExpressionMatch match = sPatternUnitRx.match( pattern );
|
|
if ( match.hasMatch() )
|
|
{
|
|
const QStringList patternValues = match.captured( 1 ).split( ' ' );
|
|
QVector< qreal > dashPattern;
|
|
Qgis::RenderUnit patternUnits = Qgis::RenderUnit::Millimeters;
|
|
for ( const QString &val : patternValues )
|
|
{
|
|
double length;
|
|
convertSize( val + match.captured( 2 ), length, patternUnits );
|
|
dashPattern.push_back( length * lineWidth * 2 );
|
|
}
|
|
|
|
simpleLine->setCustomDashVector( dashPattern );
|
|
simpleLine->setCustomDashPatternUnit( patternUnits );
|
|
simpleLine->setUseCustomDashPattern( true );
|
|
}
|
|
}
|
|
|
|
Qt::PenCapStyle capStyle = Qt::FlatCap;
|
|
Qt::PenJoinStyle joinStyle = Qt::MiterJoin;
|
|
// workaround https://github.com/OSGeo/gdal/pull/3509 in older GDAL versions
|
|
const QString id = lineStyle.value( QStringLiteral( "id" ) ).toString();
|
|
if ( id.contains( QLatin1String( "mapinfo-pen" ), Qt::CaseInsensitive ) )
|
|
{
|
|
// MapInfo renders all lines using a round pen cap and round pen join
|
|
// which are not the default values for OGR pen cap/join styles. So we need to explicitly
|
|
// override the OGR default values here on older GDAL versions
|
|
capStyle = Qt::RoundCap;
|
|
joinStyle = Qt::RoundJoin;
|
|
}
|
|
|
|
// pen cap
|
|
const QString penCap = lineStyle.value( QStringLiteral( "cap" ) ).toString();
|
|
if ( penCap.compare( QLatin1String( "b" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capStyle = Qt::FlatCap;
|
|
}
|
|
else if ( penCap.compare( QLatin1String( "r" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capStyle = Qt::RoundCap;
|
|
}
|
|
else if ( penCap.compare( QLatin1String( "p" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capStyle = Qt::SquareCap;
|
|
}
|
|
simpleLine->setPenCapStyle( capStyle );
|
|
|
|
// pen join
|
|
const QString penJoin = lineStyle.value( QStringLiteral( "j" ) ).toString();
|
|
if ( penJoin.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
joinStyle = Qt::MiterJoin;
|
|
}
|
|
else if ( penJoin.compare( QLatin1String( "r" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
joinStyle = Qt::RoundJoin;
|
|
}
|
|
else if ( penJoin.compare( QLatin1String( "b" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
joinStyle = Qt::BevelJoin;
|
|
}
|
|
simpleLine->setPenJoinStyle( joinStyle );
|
|
|
|
const QString priority = lineStyle.value( QStringLiteral( "l" ) ).toString();
|
|
if ( !priority.isEmpty() )
|
|
{
|
|
simpleLine->setRenderingPass( priority.toInt() );
|
|
}
|
|
return std::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << simpleLine.release() );
|
|
};
|
|
|
|
auto convertBrush = [&convertColor]( const QVariantMap & brushStyle ) -> std::unique_ptr< QgsSymbol >
|
|
{
|
|
const QColor foreColor = convertColor( brushStyle.value( QStringLiteral( "fc" ), QStringLiteral( "#000000" ) ).toString() );
|
|
const QColor backColor = convertColor( brushStyle.value( QStringLiteral( "bc" ), QString() ).toString() );
|
|
|
|
const QString id = brushStyle.value( QStringLiteral( "id" ) ).toString();
|
|
|
|
// if the pen is a mapinfo brush, use dedicated converter for more accurate results
|
|
const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-brush-(\\d+)" ) );
|
|
const QRegularExpressionMatch match = sMapInfoId.match( id );
|
|
if ( match.hasMatch() )
|
|
{
|
|
const int brushId = match.captured( 1 ).toInt();
|
|
QgsMapInfoSymbolConversionContext context;
|
|
std::unique_ptr<QgsSymbol> res( QgsMapInfoSymbolConverter::convertFillSymbol( brushId, context, foreColor, backColor ) );
|
|
if ( res )
|
|
return res;
|
|
}
|
|
|
|
const thread_local QRegularExpression sOgrId = QRegularExpression( QStringLiteral( "ogr-brush-(\\d+)" ) );
|
|
const QRegularExpressionMatch ogrMatch = sOgrId.match( id );
|
|
|
|
Qt::BrushStyle style = Qt::SolidPattern;
|
|
if ( ogrMatch.hasMatch() )
|
|
{
|
|
const int brushId = ogrMatch.captured( 1 ).toInt();
|
|
switch ( brushId )
|
|
{
|
|
case 0:
|
|
style = Qt::SolidPattern;
|
|
break;
|
|
|
|
case 1:
|
|
style = Qt::NoBrush;
|
|
break;
|
|
|
|
case 2:
|
|
style = Qt::HorPattern;
|
|
break;
|
|
|
|
case 3:
|
|
style = Qt::VerPattern;
|
|
break;
|
|
|
|
case 4:
|
|
style = Qt::FDiagPattern;
|
|
break;
|
|
|
|
case 5:
|
|
style = Qt::BDiagPattern;
|
|
break;
|
|
|
|
case 6:
|
|
style = Qt::CrossPattern;
|
|
break;
|
|
|
|
case 7:
|
|
style = Qt::DiagCrossPattern;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QgsSymbolLayerList layers;
|
|
if ( backColor.isValid() && style != Qt::SolidPattern && style != Qt::NoBrush )
|
|
{
|
|
std::unique_ptr< QgsSimpleFillSymbolLayer > backgroundFill = std::make_unique< QgsSimpleFillSymbolLayer >( backColor );
|
|
backgroundFill->setLocked( true );
|
|
backgroundFill->setStrokeStyle( Qt::NoPen );
|
|
layers << backgroundFill.release();
|
|
}
|
|
|
|
std::unique_ptr< QgsSimpleFillSymbolLayer > foregroundFill = std::make_unique< QgsSimpleFillSymbolLayer >( foreColor );
|
|
foregroundFill->setBrushStyle( style );
|
|
foregroundFill->setStrokeStyle( Qt::NoPen );
|
|
|
|
const QString priority = brushStyle.value( QStringLiteral( "l" ) ).toString();
|
|
if ( !priority.isEmpty() )
|
|
{
|
|
foregroundFill->setRenderingPass( priority.toInt() );
|
|
}
|
|
layers << foregroundFill.release();
|
|
return std::make_unique< QgsFillSymbol >( layers );
|
|
};
|
|
|
|
auto convertSymbol = [&convertColor, &convertSize, string]( const QVariantMap & symbolStyle ) -> std::unique_ptr< QgsSymbol >
|
|
{
|
|
const QColor color = convertColor( symbolStyle.value( QStringLiteral( "c" ), QStringLiteral( "#000000" ) ).toString() );
|
|
|
|
double symbolSize = DEFAULT_SIMPLEMARKER_SIZE;
|
|
Qgis::RenderUnit symbolSizeUnit = Qgis::RenderUnit::Millimeters;
|
|
convertSize( symbolStyle.value( QStringLiteral( "s" ) ).toString(), symbolSize, symbolSizeUnit );
|
|
|
|
const double angle = symbolStyle.value( QStringLiteral( "a" ), QStringLiteral( "0" ) ).toDouble();
|
|
|
|
const QString id = symbolStyle.value( QStringLiteral( "id" ) ).toString();
|
|
|
|
// if the symbol is a mapinfo symbol, use dedicated converter for more accurate results
|
|
const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-sym-(\\d+)" ) );
|
|
const QRegularExpressionMatch match = sMapInfoId.match( id );
|
|
if ( match.hasMatch() )
|
|
{
|
|
const int symbolId = match.captured( 1 ).toInt();
|
|
QgsMapInfoSymbolConversionContext context;
|
|
|
|
// ogr interpretations of mapinfo symbol sizes are too large -- scale these down
|
|
symbolSize *= 0.61;
|
|
|
|
std::unique_ptr<QgsSymbol> res( QgsMapInfoSymbolConverter::convertMarkerSymbol( symbolId, context, color, symbolSize, symbolSizeUnit ) );
|
|
if ( res )
|
|
return res;
|
|
}
|
|
|
|
std::unique_ptr< QgsMarkerSymbolLayer > markerLayer;
|
|
|
|
const thread_local QRegularExpression sFontId = QRegularExpression( QStringLiteral( "font-sym-(\\d+)" ) );
|
|
const QRegularExpressionMatch fontMatch = sFontId.match( id );
|
|
if ( fontMatch.hasMatch() )
|
|
{
|
|
const int symId = fontMatch.captured( 1 ).toInt();
|
|
const QStringList families = symbolStyle.value( QStringLiteral( "f" ), QString() ).toString().split( ',' );
|
|
|
|
bool familyFound = false;
|
|
QString fontFamily;
|
|
QString matched;
|
|
for ( const QString &family : std::as_const( families ) )
|
|
{
|
|
const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( family );
|
|
|
|
if ( QgsFontUtils::fontFamilyMatchOnSystem( processedFamily ) ||
|
|
QgsApplication::fontManager()->tryToDownloadFontFamily( processedFamily, matched ) )
|
|
{
|
|
familyFound = true;
|
|
fontFamily = processedFamily;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( familyFound )
|
|
{
|
|
std::unique_ptr< QgsFontMarkerSymbolLayer > fontMarker = std::make_unique< QgsFontMarkerSymbolLayer >( fontFamily, QChar( symId ), symbolSize );
|
|
fontMarker->setSizeUnit( symbolSizeUnit );
|
|
fontMarker->setAngle( -angle );
|
|
|
|
fontMarker->setColor( color );
|
|
|
|
const QColor strokeColor = convertColor( symbolStyle.value( QStringLiteral( "o" ), QString() ).toString() );
|
|
if ( strokeColor.isValid() )
|
|
{
|
|
fontMarker->setStrokeColor( strokeColor );
|
|
fontMarker->setStrokeWidth( 1 );
|
|
fontMarker->setStrokeWidthUnit( Qgis::RenderUnit::Points );
|
|
}
|
|
else
|
|
{
|
|
fontMarker->setStrokeWidth( 0 );
|
|
}
|
|
|
|
markerLayer = std::move( fontMarker );
|
|
}
|
|
else if ( !families.empty() )
|
|
{
|
|
// couldn't even find a matching font in the backup list
|
|
QgsMessageLog::logMessage( QObject::tr( "Font %1 not found on system" ).arg( families.at( 0 ) ) );
|
|
}
|
|
}
|
|
|
|
if ( !markerLayer )
|
|
{
|
|
const thread_local QRegularExpression sOgrId = QRegularExpression( QStringLiteral( "ogr-sym-(\\d+)" ) );
|
|
const QRegularExpressionMatch ogrMatch = sOgrId.match( id );
|
|
|
|
Qgis::MarkerShape shape;
|
|
bool isFilled = true;
|
|
if ( ogrMatch.hasMatch() )
|
|
{
|
|
const int symId = ogrMatch.captured( 1 ).toInt();
|
|
switch ( symId )
|
|
{
|
|
case 0:
|
|
shape = Qgis::MarkerShape::Cross;
|
|
break;
|
|
|
|
case 1:
|
|
shape = Qgis::MarkerShape::Cross2;
|
|
break;
|
|
|
|
case 2:
|
|
isFilled = false;
|
|
shape = Qgis::MarkerShape::Circle;
|
|
break;
|
|
|
|
case 3:
|
|
shape = Qgis::MarkerShape::Circle;
|
|
break;
|
|
|
|
case 4:
|
|
isFilled = false;
|
|
shape = Qgis::MarkerShape::Square;
|
|
break;
|
|
|
|
case 5:
|
|
shape = Qgis::MarkerShape::Square;
|
|
break;
|
|
|
|
case 6:
|
|
isFilled = false;
|
|
shape = Qgis::MarkerShape::Triangle;
|
|
break;
|
|
|
|
case 7:
|
|
shape = Qgis::MarkerShape::Triangle;
|
|
break;
|
|
|
|
case 8:
|
|
isFilled = false;
|
|
shape = Qgis::MarkerShape::Star;
|
|
break;
|
|
|
|
case 9:
|
|
shape = Qgis::MarkerShape::Star;
|
|
break;
|
|
|
|
case 10:
|
|
shape = Qgis::MarkerShape::Line;
|
|
break;
|
|
|
|
default:
|
|
isFilled = false;
|
|
shape = Qgis::MarkerShape::Square; // to initialize the variable
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isFilled = false;
|
|
shape = Qgis::MarkerShape::Square; // to initialize the variable
|
|
}
|
|
|
|
std::unique_ptr< QgsSimpleMarkerSymbolLayer > simpleMarker = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, symbolSize, -angle );
|
|
simpleMarker->setSizeUnit( symbolSizeUnit );
|
|
simpleMarker->setStrokeWidth( 1.0 );
|
|
simpleMarker->setStrokeWidthUnit( Qgis::RenderUnit::Points );
|
|
|
|
if ( isFilled && QgsSimpleMarkerSymbolLayer::shapeIsFilled( shape ) )
|
|
{
|
|
simpleMarker->setColor( color );
|
|
simpleMarker->setStrokeStyle( Qt::NoPen );
|
|
}
|
|
else
|
|
{
|
|
simpleMarker->setFillColor( QColor( 0, 0, 0, 0 ) );
|
|
simpleMarker->setStrokeColor( color );
|
|
}
|
|
|
|
const QColor strokeColor = convertColor( symbolStyle.value( QStringLiteral( "o" ), QString() ).toString() );
|
|
if ( strokeColor.isValid() )
|
|
{
|
|
simpleMarker->setStrokeColor( strokeColor );
|
|
simpleMarker->setStrokeStyle( Qt::SolidLine );
|
|
}
|
|
|
|
markerLayer = std::move( simpleMarker );
|
|
}
|
|
|
|
return std::make_unique< QgsMarkerSymbol >( QgsSymbolLayerList() << markerLayer.release() );
|
|
};
|
|
|
|
switch ( type )
|
|
{
|
|
case Qgis::SymbolType::Marker:
|
|
if ( styles.contains( QStringLiteral( "symbol" ) ) )
|
|
{
|
|
const QVariantMap symbolStyle = styles.value( QStringLiteral( "symbol" ) ).toMap();
|
|
return convertSymbol( symbolStyle );
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
case Qgis::SymbolType::Line:
|
|
if ( styles.contains( QStringLiteral( "pen" ) ) )
|
|
{
|
|
// line symbol type
|
|
const QVariantMap lineStyle = styles.value( QStringLiteral( "pen" ) ).toMap();
|
|
return convertPen( lineStyle );
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
case Qgis::SymbolType::Fill:
|
|
{
|
|
std::unique_ptr< QgsSymbol > fillSymbol = std::make_unique< QgsFillSymbol >();
|
|
if ( styles.contains( QStringLiteral( "brush" ) ) )
|
|
{
|
|
const QVariantMap brushStyle = styles.value( QStringLiteral( "brush" ) ).toMap();
|
|
fillSymbol = convertBrush( brushStyle );
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr< QgsSimpleFillSymbolLayer > emptyFill = std::make_unique< QgsSimpleFillSymbolLayer >();
|
|
emptyFill->setBrushStyle( Qt::NoBrush );
|
|
fillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << emptyFill.release() );
|
|
}
|
|
|
|
std::unique_ptr< QgsSymbol > penSymbol;
|
|
if ( styles.contains( QStringLiteral( "pen" ) ) )
|
|
{
|
|
const QVariantMap lineStyle = styles.value( QStringLiteral( "pen" ) ).toMap();
|
|
penSymbol = convertPen( lineStyle );
|
|
}
|
|
|
|
if ( penSymbol )
|
|
{
|
|
const int count = penSymbol->symbolLayerCount();
|
|
|
|
if ( count == 1 )
|
|
{
|
|
// 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
|
|
if ( QgsSymbolLayerUtils::condenseFillAndOutline( dynamic_cast< QgsFillSymbolLayer * >( fillSymbol->symbolLayer( fillSymbol->symbolLayerCount() - 1 ) ),
|
|
dynamic_cast< QgsLineSymbolLayer * >( penSymbol->symbolLayer( 0 ) ) ) )
|
|
return fillSymbol;
|
|
}
|
|
|
|
for ( int i = 0; i < count; ++i )
|
|
{
|
|
std::unique_ptr< QgsSymbolLayer > layer( penSymbol->takeSymbolLayer( 0 ) );
|
|
layer->setLocked( true );
|
|
fillSymbol->appendSymbolLayer( layer.release() );
|
|
}
|
|
}
|
|
|
|
return fillSymbol;
|
|
}
|
|
|
|
case Qgis::SymbolType::Hybrid:
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void QgsOgrUtils::ogrFieldTypeToQVariantType( OGRFieldType ogrType, OGRFieldSubType ogrSubType, QVariant::Type &variantType, QVariant::Type &variantSubType )
|
|
{
|
|
variantType = QVariant::Type::Invalid;
|
|
variantSubType = QVariant::Type::Invalid;
|
|
|
|
switch ( ogrType )
|
|
{
|
|
case OFTInteger:
|
|
if ( ogrSubType == OFSTBoolean )
|
|
{
|
|
variantType = QVariant::Bool;
|
|
ogrSubType = OFSTBoolean;
|
|
}
|
|
else
|
|
variantType = QVariant::Int;
|
|
break;
|
|
case OFTInteger64:
|
|
variantType = QVariant::LongLong;
|
|
break;
|
|
case OFTReal:
|
|
variantType = QVariant::Double;
|
|
break;
|
|
case OFTDate:
|
|
variantType = QVariant::Date;
|
|
break;
|
|
case OFTTime:
|
|
variantType = QVariant::Time;
|
|
break;
|
|
case OFTDateTime:
|
|
variantType = QVariant::DateTime;
|
|
break;
|
|
|
|
case OFTBinary:
|
|
variantType = QVariant::ByteArray;
|
|
break;
|
|
|
|
case OFTString:
|
|
case OFTWideString:
|
|
if ( ogrSubType == OFSTJSON )
|
|
{
|
|
ogrSubType = OFSTJSON;
|
|
variantType = QVariant::Map;
|
|
variantSubType = QVariant::String;
|
|
}
|
|
else
|
|
{
|
|
variantType = QVariant::String;
|
|
}
|
|
break;
|
|
|
|
case OFTStringList:
|
|
case OFTWideStringList:
|
|
variantType = QVariant::StringList;
|
|
variantSubType = QVariant::String;
|
|
break;
|
|
|
|
case OFTIntegerList:
|
|
variantType = QVariant::List;
|
|
variantSubType = QVariant::Int;
|
|
break;
|
|
|
|
case OFTRealList:
|
|
variantType = QVariant::List;
|
|
variantSubType = QVariant::Double;
|
|
break;
|
|
|
|
case OFTInteger64List:
|
|
variantType = QVariant::List;
|
|
variantSubType = QVariant::LongLong;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void QgsOgrUtils::variantTypeToOgrFieldType( QVariant::Type variantType, OGRFieldType &ogrType, OGRFieldSubType &ogrSubType )
|
|
{
|
|
ogrSubType = OFSTNone;
|
|
switch ( variantType )
|
|
{
|
|
case QVariant::Bool:
|
|
ogrType = OFTInteger;
|
|
ogrSubType = OFSTBoolean;
|
|
break;
|
|
|
|
case QVariant::Int:
|
|
ogrType = OFTInteger;
|
|
break;
|
|
|
|
case QVariant::LongLong:
|
|
ogrType = OFTInteger64;
|
|
break;
|
|
|
|
case QVariant::Double:
|
|
ogrType = OFTReal;
|
|
break;
|
|
|
|
case QVariant::Char:
|
|
ogrType = OFTString;
|
|
break;
|
|
|
|
case QVariant::String:
|
|
ogrType = OFTString;
|
|
break;
|
|
|
|
case QVariant::StringList:
|
|
ogrType = OFTStringList;
|
|
break;
|
|
|
|
case QVariant::ByteArray:
|
|
ogrType = OFTBinary;
|
|
break;
|
|
|
|
case QVariant::Date:
|
|
ogrType = OFTDate;
|
|
break;
|
|
|
|
case QVariant::Time:
|
|
ogrType = OFTTime;
|
|
break;
|
|
case QVariant::DateTime:
|
|
ogrType = OFTDateTime;
|
|
break;
|
|
|
|
default:
|
|
ogrType = OFTString;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QVariant QgsOgrUtils::stringToVariant( OGRFieldType type, OGRFieldSubType, const QString &string )
|
|
{
|
|
if ( string.isEmpty() )
|
|
return QVariant();
|
|
|
|
bool ok = false;
|
|
QVariant res;
|
|
switch ( type )
|
|
{
|
|
case OFTInteger:
|
|
res = string.toInt( &ok );
|
|
break;
|
|
|
|
case OFTInteger64:
|
|
res = string.toLongLong( &ok );
|
|
break;
|
|
|
|
case OFTReal:
|
|
res = string.toDouble( &ok );
|
|
break;
|
|
|
|
case OFTString:
|
|
case OFTWideString:
|
|
res = string;
|
|
ok = true;
|
|
break;
|
|
|
|
case OFTDate:
|
|
res = QDate::fromString( string, Qt::ISODate );
|
|
ok = res.isValid();
|
|
break;
|
|
|
|
case OFTTime:
|
|
res = QTime::fromString( string, Qt::ISODate );
|
|
ok = res.isValid();
|
|
break;
|
|
|
|
case OFTDateTime:
|
|
res = QDateTime::fromString( string, Qt::ISODate );
|
|
ok = res.isValid();
|
|
break;
|
|
|
|
default:
|
|
res = string;
|
|
ok = true;
|
|
break;
|
|
}
|
|
|
|
return ok ? res : QVariant();
|
|
}
|
|
|
|
QList<QgsVectorDataProvider::NativeType> QgsOgrUtils::nativeFieldTypesForDriver( GDALDriverH driver )
|
|
{
|
|
if ( !driver )
|
|
return {};
|
|
|
|
const QString driverName = QString::fromUtf8( GDALGetDriverShortName( driver ) );
|
|
|
|
int nMaxIntLen = 11;
|
|
int nMaxInt64Len = 21;
|
|
int nMaxDoubleLen = 20;
|
|
int nMaxDoublePrec = 15;
|
|
int nDateLen = 8;
|
|
if ( driverName == QLatin1String( "GPKG" ) )
|
|
{
|
|
// GPKG only supports field length for text (and binary)
|
|
nMaxIntLen = 0;
|
|
nMaxInt64Len = 0;
|
|
nMaxDoubleLen = 0;
|
|
nMaxDoublePrec = 0;
|
|
nDateLen = 0;
|
|
}
|
|
|
|
QList<QgsVectorDataProvider::NativeType> nativeTypes;
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::Int ), QStringLiteral( "integer" ), QVariant::Int, 0, nMaxIntLen )
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::LongLong ), QStringLiteral( "integer64" ), QVariant::LongLong, 0, nMaxInt64Len )
|
|
<< QgsVectorDataProvider::NativeType( QObject::tr( "Decimal number (real)" ), QStringLiteral( "double" ), QVariant::Double, 0, nMaxDoubleLen, 0, nMaxDoublePrec )
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::String ), QStringLiteral( "string" ), QVariant::String, 0, 65535 );
|
|
|
|
if ( driverName == QLatin1String( "GPKG" ) )
|
|
nativeTypes << QgsVectorDataProvider::NativeType( QObject::tr( "JSON (string)" ), QStringLiteral( "JSON" ), QVariant::Map, 0, 0, 0, 0, QVariant::String );
|
|
|
|
bool supportsDate = true;
|
|
bool supportsTime = true;
|
|
bool supportsDateTime = true;
|
|
bool supportsBinary = false;
|
|
bool supportIntegerList = false;
|
|
bool supportInteger64List = false;
|
|
bool supportRealList = false;
|
|
bool supportsStringList = false;
|
|
|
|
// For drivers that advertise their data type, use that instead of the
|
|
// above hardcoded defaults.
|
|
if ( const char *pszDataTypes = GDALGetMetadataItem( driver, GDAL_DMD_CREATIONFIELDDATATYPES, nullptr ) )
|
|
{
|
|
char **papszTokens = CSLTokenizeString2( pszDataTypes, " ", 0 );
|
|
supportsDate = CSLFindString( papszTokens, "Date" ) >= 0;
|
|
supportsTime = CSLFindString( papszTokens, "Time" ) >= 0;
|
|
supportsDateTime = CSLFindString( papszTokens, "DateTime" ) >= 0;
|
|
supportsBinary = CSLFindString( papszTokens, "Binary" ) >= 0;
|
|
supportIntegerList = CSLFindString( papszTokens, "IntegerList" ) >= 0;
|
|
supportInteger64List = CSLFindString( papszTokens, "Integer64List" ) >= 0;
|
|
supportRealList = CSLFindString( papszTokens, "RealList" ) >= 0;
|
|
supportsStringList = CSLFindString( papszTokens, "StringList" ) >= 0;
|
|
CSLDestroy( papszTokens );
|
|
}
|
|
|
|
// Older versions of GDAL incorrectly report that shapefiles support
|
|
// DateTime.
|
|
#if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,2,0)
|
|
if ( driverName == QLatin1String( "ESRI Shapefile" ) )
|
|
{
|
|
supportsDateTime = false;
|
|
}
|
|
#endif
|
|
|
|
if ( supportsDate )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::Date ), QStringLiteral( "date" ), QVariant::Date, nDateLen, nDateLen );
|
|
}
|
|
if ( supportsTime )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::Time ), QStringLiteral( "time" ), QVariant::Time );
|
|
}
|
|
if ( supportsDateTime )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::DateTime ), QStringLiteral( "datetime" ), QVariant::DateTime );
|
|
}
|
|
if ( supportsBinary )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::ByteArray ), QStringLiteral( "binary" ), QVariant::ByteArray );
|
|
}
|
|
if ( supportIntegerList )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::List, QVariant::Int ), QStringLiteral( "integerlist" ), QVariant::List, 0, 0, 0, 0, QVariant::Int );
|
|
}
|
|
if ( supportInteger64List )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::List, QVariant::LongLong ), QStringLiteral( "integer64list" ), QVariant::List, 0, 0, 0, 0, QVariant::LongLong );
|
|
}
|
|
if ( supportRealList )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::List, QVariant::Double ), QStringLiteral( "doublelist" ), QVariant::List, 0, 0, 0, 0, QVariant::Double );
|
|
}
|
|
if ( supportsStringList )
|
|
{
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QVariant::StringList ), QStringLiteral( "stringlist" ), QVariant::List, 0, 0, 0, 0, QVariant::String );
|
|
}
|
|
|
|
const char *pszDataSubTypes = GDALGetMetadataItem( driver, GDAL_DMD_CREATIONFIELDDATASUBTYPES, nullptr );
|
|
if ( pszDataSubTypes && strstr( pszDataSubTypes, "Boolean" ) )
|
|
{
|
|
// boolean data type
|
|
nativeTypes
|
|
<< QgsVectorDataProvider::NativeType( QObject::tr( "Boolean" ), QStringLiteral( "bool" ), QVariant::Bool );
|
|
}
|
|
|
|
return nativeTypes;
|
|
}
|
|
|
|
|
|
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,3,0)
|
|
std::unique_ptr< QgsFieldDomain > QgsOgrUtils::convertFieldDomain( OGRFieldDomainH domain )
|
|
{
|
|
if ( !domain )
|
|
return nullptr;
|
|
|
|
const QString name{ OGR_FldDomain_GetName( domain ) };
|
|
const QString description{ OGR_FldDomain_GetDescription( domain ) };
|
|
|
|
QVariant::Type fieldType = QVariant::Type::Invalid;
|
|
QVariant::Type fieldSubType = QVariant::Type::Invalid;
|
|
const OGRFieldType domainFieldType = OGR_FldDomain_GetFieldType( domain );
|
|
const OGRFieldSubType domainFieldSubType = OGR_FldDomain_GetFieldSubType( domain );
|
|
ogrFieldTypeToQVariantType( domainFieldType, domainFieldSubType, fieldType, fieldSubType );
|
|
|
|
std::unique_ptr< QgsFieldDomain > res;
|
|
switch ( OGR_FldDomain_GetDomainType( domain ) )
|
|
{
|
|
case OFDT_CODED:
|
|
{
|
|
QList< QgsCodedValue > values;
|
|
const OGRCodedValue *codedValue = OGR_CodedFldDomain_GetEnumeration( domain );
|
|
while ( codedValue && codedValue->pszCode )
|
|
{
|
|
const QString code( codedValue->pszCode );
|
|
|
|
// if pszValue is null then it indicates we are working with a set of acceptable values which aren't
|
|
// coded. In this case we copy the code as the value so that QGIS exposes the domain as a choice of
|
|
// the valid code values.
|
|
const QString value( codedValue->pszValue ? codedValue->pszValue : codedValue->pszCode );
|
|
values.append( QgsCodedValue( stringToVariant( domainFieldType, domainFieldSubType, code ), value ) );
|
|
|
|
codedValue++;
|
|
}
|
|
|
|
res = std::make_unique< QgsCodedFieldDomain >( name, description, fieldType, values );
|
|
break;
|
|
}
|
|
|
|
case OFDT_RANGE:
|
|
{
|
|
QVariant minValue;
|
|
bool minIsInclusive = false;
|
|
if ( const OGRField *min = OGR_RangeFldDomain_GetMin( domain, &minIsInclusive ) )
|
|
{
|
|
minValue = QgsOgrUtils::OGRFieldtoVariant( min, domainFieldType );
|
|
}
|
|
QVariant maxValue;
|
|
bool maxIsInclusive = false;
|
|
if ( const OGRField *max = OGR_RangeFldDomain_GetMax( domain, &maxIsInclusive ) )
|
|
{
|
|
maxValue = QgsOgrUtils::OGRFieldtoVariant( max, domainFieldType );
|
|
}
|
|
|
|
res = std::make_unique< QgsRangeFieldDomain >( name, description, fieldType,
|
|
minValue, minIsInclusive,
|
|
maxValue, maxIsInclusive );
|
|
break;
|
|
}
|
|
|
|
case OFDT_GLOB:
|
|
res = std::make_unique< QgsGlobFieldDomain >( name, description, fieldType,
|
|
QString( OGR_GlobFldDomain_GetGlob( domain ) ) );
|
|
break;
|
|
}
|
|
|
|
switch ( OGR_FldDomain_GetMergePolicy( domain ) )
|
|
{
|
|
case OFDMP_DEFAULT_VALUE:
|
|
res->setMergePolicy( Qgis::FieldDomainMergePolicy::DefaultValue );
|
|
break;
|
|
case OFDMP_SUM:
|
|
res->setMergePolicy( Qgis::FieldDomainMergePolicy::Sum );
|
|
break;
|
|
case OFDMP_GEOMETRY_WEIGHTED:
|
|
res->setMergePolicy( Qgis::FieldDomainMergePolicy::GeometryWeighted );
|
|
break;
|
|
}
|
|
|
|
switch ( OGR_FldDomain_GetSplitPolicy( domain ) )
|
|
{
|
|
case OFDSP_DEFAULT_VALUE:
|
|
res->setSplitPolicy( Qgis::FieldDomainSplitPolicy::DefaultValue );
|
|
break;
|
|
case OFDSP_DUPLICATE:
|
|
res->setSplitPolicy( Qgis::FieldDomainSplitPolicy::Duplicate );
|
|
break;
|
|
case OFDSP_GEOMETRY_RATIO:
|
|
res->setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio );
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain )
|
|
{
|
|
if ( !domain )
|
|
return nullptr;
|
|
|
|
OGRFieldType domainFieldType = OFTInteger;
|
|
OGRFieldSubType domainFieldSubType = OFSTNone;
|
|
variantTypeToOgrFieldType( domain->fieldType(), domainFieldType, domainFieldSubType );
|
|
|
|
OGRFieldDomainH res = nullptr;
|
|
switch ( domain->type() )
|
|
{
|
|
case Qgis::FieldDomainType::Coded:
|
|
{
|
|
std::vector< OGRCodedValue > enumeration;
|
|
const QList< QgsCodedValue> values = qgis::down_cast< const QgsCodedFieldDomain * >( domain )->values();
|
|
enumeration.reserve( values.size() );
|
|
for ( const QgsCodedValue &value : values )
|
|
{
|
|
OGRCodedValue codedValue;
|
|
codedValue.pszCode = CPLStrdup( value.code().toString().toUtf8().constData() );
|
|
codedValue.pszValue = CPLStrdup( value.value().toUtf8().constData() );
|
|
enumeration.push_back( codedValue );
|
|
}
|
|
OGRCodedValue last;
|
|
last.pszCode = nullptr;
|
|
last.pszValue = nullptr;
|
|
enumeration.push_back( last );
|
|
res = OGR_CodedFldDomain_Create(
|
|
domain->name().toUtf8().constData(),
|
|
domain->description().toUtf8().constData(),
|
|
domainFieldType,
|
|
domainFieldSubType,
|
|
enumeration.data()
|
|
);
|
|
|
|
for ( const OGRCodedValue &value : std::as_const( enumeration ) )
|
|
{
|
|
CPLFree( value.pszCode );
|
|
CPLFree( value.pszValue );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Qgis::FieldDomainType::Range:
|
|
{
|
|
std::unique_ptr< OGRField > min = variantToOGRField( qgis::down_cast< const QgsRangeFieldDomain * >( domain )->minimum(), domainFieldType );
|
|
std::unique_ptr< OGRField > max = variantToOGRField( qgis::down_cast< const QgsRangeFieldDomain * >( domain )->maximum(), domainFieldType );
|
|
if ( !min || !max )
|
|
return nullptr;
|
|
res = OGR_RangeFldDomain_Create(
|
|
domain->name().toUtf8().constData(),
|
|
domain->description().toUtf8().constData(),
|
|
domainFieldType,
|
|
domainFieldSubType,
|
|
min.get(),
|
|
qgis::down_cast< const QgsRangeFieldDomain * >( domain )->minimumIsInclusive(),
|
|
max.get(),
|
|
qgis::down_cast< const QgsRangeFieldDomain * >( domain )->maximumIsInclusive()
|
|
);
|
|
break;
|
|
}
|
|
|
|
case Qgis::FieldDomainType::Glob:
|
|
{
|
|
res = OGR_GlobFldDomain_Create(
|
|
domain->name().toUtf8().constData(),
|
|
domain->description().toUtf8().constData(),
|
|
domainFieldType,
|
|
domainFieldSubType,
|
|
qgis::down_cast< const QgsGlobFieldDomain * >( domain )->glob().toUtf8().constData()
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch ( domain->mergePolicy() )
|
|
{
|
|
case Qgis::FieldDomainMergePolicy::DefaultValue:
|
|
OGR_FldDomain_SetMergePolicy( res, OFDMP_DEFAULT_VALUE );
|
|
break;
|
|
case Qgis::FieldDomainMergePolicy::GeometryWeighted:
|
|
OGR_FldDomain_SetMergePolicy( res, OFDMP_GEOMETRY_WEIGHTED );
|
|
break;
|
|
case Qgis::FieldDomainMergePolicy::Sum:
|
|
OGR_FldDomain_SetMergePolicy( res, OFDMP_SUM );
|
|
break;
|
|
}
|
|
|
|
switch ( domain->splitPolicy() )
|
|
{
|
|
case Qgis::FieldDomainSplitPolicy::DefaultValue:
|
|
OGR_FldDomain_SetSplitPolicy( res, OFDSP_DEFAULT_VALUE );
|
|
break;
|
|
case Qgis::FieldDomainSplitPolicy::GeometryRatio:
|
|
OGR_FldDomain_SetSplitPolicy( res, OFDSP_GEOMETRY_RATIO );
|
|
break;
|
|
case Qgis::FieldDomainSplitPolicy::Duplicate:
|
|
OGR_FldDomain_SetSplitPolicy( res, OFDSP_DUPLICATE );
|
|
break;
|
|
|
|
case Qgis::FieldDomainSplitPolicy::UnsetField:
|
|
// not supported
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
|
|
QgsWeakRelation QgsOgrUtils::convertRelationship( GDALRelationshipH relationship, const QString &datasetUri )
|
|
{
|
|
QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
|
|
const QVariantMap datasetUriParts = ogrProviderMetadata->decodeUri( datasetUri );
|
|
|
|
const QString leftTableName( GDALRelationshipGetLeftTableName( relationship ) );
|
|
|
|
QVariantMap leftTableUriParts = datasetUriParts;
|
|
leftTableUriParts.insert( QStringLiteral( "layerName" ), leftTableName );
|
|
const QString leftTableSource = ogrProviderMetadata->encodeUri( leftTableUriParts );
|
|
|
|
const QString rightTableName( GDALRelationshipGetRightTableName( relationship ) );
|
|
QVariantMap rightTableUriParts = datasetUriParts;
|
|
rightTableUriParts.insert( QStringLiteral( "layerName" ), rightTableName );
|
|
const QString rightTableSource = ogrProviderMetadata->encodeUri( rightTableUriParts );
|
|
|
|
const QString mappingTableName( GDALRelationshipGetMappingTableName( relationship ) );
|
|
QString mappingTableSource;
|
|
if ( !mappingTableName.isEmpty() )
|
|
{
|
|
QVariantMap mappingTableUriParts = datasetUriParts;
|
|
mappingTableUriParts.insert( QStringLiteral( "layerName" ), mappingTableName );
|
|
mappingTableSource = ogrProviderMetadata->encodeUri( mappingTableUriParts );
|
|
}
|
|
|
|
const QString relationshipName( GDALRelationshipGetName( relationship ) );
|
|
|
|
char **cslLeftTableFieldNames = GDALRelationshipGetLeftTableFields( relationship );
|
|
const QStringList leftTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftTableFieldNames );
|
|
CSLDestroy( cslLeftTableFieldNames );
|
|
|
|
char **cslRightTableFieldNames = GDALRelationshipGetRightTableFields( relationship );
|
|
const QStringList rightTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightTableFieldNames );
|
|
CSLDestroy( cslRightTableFieldNames );
|
|
|
|
char **cslLeftMappingTableFieldNames = GDALRelationshipGetLeftMappingTableFields( relationship );
|
|
const QStringList leftMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftMappingTableFieldNames );
|
|
CSLDestroy( cslLeftMappingTableFieldNames );
|
|
|
|
char **cslRightMappingTableFieldNames = GDALRelationshipGetRightMappingTableFields( relationship );
|
|
const QStringList rightMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightMappingTableFieldNames );
|
|
CSLDestroy( cslRightMappingTableFieldNames );
|
|
|
|
const QString forwardPathLabel( GDALRelationshipGetForwardPathLabel( relationship ) );
|
|
const QString backwardPathLabel( GDALRelationshipGetBackwardPathLabel( relationship ) );
|
|
const QString relatedTableType( GDALRelationshipGetRelatedTableType( relationship ) );
|
|
|
|
const GDALRelationshipType relationshipType = GDALRelationshipGetType( relationship );
|
|
Qgis::RelationshipStrength strength = Qgis::RelationshipStrength::Association;
|
|
switch ( relationshipType )
|
|
{
|
|
case GRT_COMPOSITE:
|
|
strength = Qgis::RelationshipStrength::Composition;
|
|
break;
|
|
|
|
case GRT_ASSOCIATION:
|
|
strength = Qgis::RelationshipStrength::Association;
|
|
break;
|
|
|
|
case GRT_AGGREGATION:
|
|
QgsLogger::warning( "Aggregation relationships are not supported, treating as association instead" );
|
|
break;
|
|
}
|
|
|
|
const GDALRelationshipCardinality eCardinality = GDALRelationshipGetCardinality( relationship );
|
|
Qgis::RelationshipCardinality cardinality = Qgis::RelationshipCardinality::OneToOne;
|
|
switch ( eCardinality )
|
|
{
|
|
case GRC_ONE_TO_ONE:
|
|
cardinality = Qgis::RelationshipCardinality::OneToOne;
|
|
break;
|
|
case GRC_ONE_TO_MANY:
|
|
cardinality = Qgis::RelationshipCardinality::OneToMany;
|
|
break;
|
|
case GRC_MANY_TO_ONE:
|
|
cardinality = Qgis::RelationshipCardinality::ManyToOne;
|
|
break;
|
|
case GRC_MANY_TO_MANY:
|
|
cardinality = Qgis::RelationshipCardinality::ManyToMany;
|
|
break;
|
|
}
|
|
|
|
switch ( cardinality )
|
|
{
|
|
case Qgis::RelationshipCardinality::OneToOne:
|
|
case Qgis::RelationshipCardinality::OneToMany:
|
|
case Qgis::RelationshipCardinality::ManyToOne:
|
|
{
|
|
QgsWeakRelation rel( relationshipName,
|
|
relationshipName,
|
|
strength,
|
|
QString(), QString(), rightTableSource, QStringLiteral( "ogr" ),
|
|
QString(), QString(), leftTableSource, QStringLiteral( "ogr" ) );
|
|
rel.setCardinality( cardinality );
|
|
rel.setForwardPathLabel( forwardPathLabel );
|
|
rel.setBackwardPathLabel( backwardPathLabel );
|
|
rel.setRelatedTableType( relatedTableType );
|
|
rel.setReferencedLayerFields( leftTableFieldNames );
|
|
rel.setReferencingLayerFields( rightTableFieldNames );
|
|
return rel;
|
|
}
|
|
|
|
case Qgis::RelationshipCardinality::ManyToMany:
|
|
{
|
|
QgsWeakRelation rel( relationshipName,
|
|
relationshipName,
|
|
strength,
|
|
QString(), QString(), rightTableSource, QStringLiteral( "ogr" ),
|
|
QString(), QString(), leftTableSource, QStringLiteral( "ogr" ) );
|
|
rel.setCardinality( cardinality );
|
|
rel.setForwardPathLabel( forwardPathLabel );
|
|
rel.setBackwardPathLabel( backwardPathLabel );
|
|
rel.setRelatedTableType( relatedTableType );
|
|
rel.setMappingTable( QgsVectorLayerRef( QString(), QString(), mappingTableSource, QStringLiteral( "ogr" ) ) );
|
|
rel.setReferencedLayerFields( leftTableFieldNames );
|
|
rel.setMappingReferencedLayerFields( leftMappingTableFieldNames );
|
|
rel.setReferencingLayerFields( rightTableFieldNames );
|
|
rel.setMappingReferencingLayerFields( rightMappingTableFieldNames );
|
|
return rel;
|
|
}
|
|
}
|
|
return QgsWeakRelation();
|
|
}
|
|
|
|
gdal::relationship_unique_ptr QgsOgrUtils::convertRelationship( const QgsWeakRelation &relationship, QString &error )
|
|
{
|
|
GDALRelationshipCardinality gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_MANY;
|
|
switch ( relationship.cardinality() )
|
|
{
|
|
case Qgis::RelationshipCardinality::OneToOne:
|
|
gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_ONE;
|
|
break;
|
|
case Qgis::RelationshipCardinality::OneToMany:
|
|
gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_MANY;
|
|
break;
|
|
case Qgis::RelationshipCardinality::ManyToOne:
|
|
gCardinality = GDALRelationshipCardinality::GRC_MANY_TO_ONE;
|
|
break;
|
|
case Qgis::RelationshipCardinality::ManyToMany:
|
|
gCardinality = GDALRelationshipCardinality::GRC_MANY_TO_MANY;
|
|
break;
|
|
}
|
|
|
|
QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
|
|
|
|
const QVariantMap leftParts = ogrProviderMetadata->decodeUri( relationship.referencedLayerSource() );
|
|
const QString leftTableName = leftParts.value( QStringLiteral( "layerName" ) ).toString();
|
|
if ( leftTableName.isEmpty() )
|
|
{
|
|
error = QObject::tr( "Parent table name was not set" );
|
|
return nullptr;
|
|
}
|
|
|
|
const QVariantMap rightParts = ogrProviderMetadata->decodeUri( relationship.referencingLayerSource() );
|
|
const QString rightTableName = rightParts.value( QStringLiteral( "layerName" ) ).toString();
|
|
if ( rightTableName.isEmpty() )
|
|
{
|
|
error = QObject::tr( "Child table name was not set" );
|
|
return nullptr;
|
|
}
|
|
|
|
if ( leftParts.value( QStringLiteral( "path" ) ).toString() != rightParts.value( QStringLiteral( "path" ) ).toString() )
|
|
{
|
|
error = QObject::tr( "Parent and child table must be from the same dataset" );
|
|
return nullptr;
|
|
}
|
|
|
|
QString mappingTableName;
|
|
if ( !relationship.mappingTableSource().isEmpty() )
|
|
{
|
|
const QVariantMap mappingParts = ogrProviderMetadata->decodeUri( relationship.mappingTableSource() );
|
|
mappingTableName = mappingParts.value( QStringLiteral( "layerName" ) ).toString();
|
|
if ( leftParts.value( QStringLiteral( "path" ) ).toString() != mappingParts.value( QStringLiteral( "path" ) ).toString() )
|
|
{
|
|
error = QObject::tr( "Parent and mapping table must be from the same dataset" );
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
gdal::relationship_unique_ptr relationH( GDALRelationshipCreate( relationship.name().toLocal8Bit().constData(),
|
|
leftTableName.toLocal8Bit().constData(),
|
|
rightTableName.toLocal8Bit().constData(),
|
|
gCardinality ) );
|
|
|
|
// set left table fields
|
|
const QStringList leftFieldNames = relationship.referencedLayerFields();
|
|
int count = leftFieldNames.count();
|
|
char **lst = new char *[count + 1];
|
|
if ( count > 0 )
|
|
{
|
|
int pos = 0;
|
|
for ( const QString &string : leftFieldNames )
|
|
{
|
|
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
|
|
pos++;
|
|
}
|
|
}
|
|
lst[count] = nullptr;
|
|
GDALRelationshipSetLeftTableFields( relationH.get(), lst );
|
|
CSLDestroy( lst );
|
|
|
|
// set right table fields
|
|
const QStringList rightFieldNames = relationship.referencingLayerFields();
|
|
count = rightFieldNames.count();
|
|
lst = new char *[count + 1];
|
|
if ( count > 0 )
|
|
{
|
|
int pos = 0;
|
|
for ( const QString &string : rightFieldNames )
|
|
{
|
|
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
|
|
pos++;
|
|
}
|
|
}
|
|
lst[count] = nullptr;
|
|
GDALRelationshipSetRightTableFields( relationH.get(), lst );
|
|
CSLDestroy( lst );
|
|
|
|
if ( !mappingTableName.isEmpty() )
|
|
{
|
|
GDALRelationshipSetMappingTableName( relationH.get(), mappingTableName.toLocal8Bit().constData() );
|
|
|
|
// set left mapping table fields
|
|
const QStringList leftFieldNames = relationship.mappingReferencedLayerFields();
|
|
int count = leftFieldNames.count();
|
|
char **lst = new char *[count + 1];
|
|
if ( count > 0 )
|
|
{
|
|
int pos = 0;
|
|
for ( const QString &string : leftFieldNames )
|
|
{
|
|
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
|
|
pos++;
|
|
}
|
|
}
|
|
lst[count] = nullptr;
|
|
GDALRelationshipSetLeftMappingTableFields( relationH.get(), lst );
|
|
CSLDestroy( lst );
|
|
|
|
// set right table fields
|
|
const QStringList rightFieldNames = relationship.mappingReferencingLayerFields();
|
|
count = rightFieldNames.count();
|
|
lst = new char *[count + 1];
|
|
if ( count > 0 )
|
|
{
|
|
int pos = 0;
|
|
for ( const QString &string : rightFieldNames )
|
|
{
|
|
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
|
|
pos++;
|
|
}
|
|
}
|
|
lst[count] = nullptr;
|
|
GDALRelationshipSetRightMappingTableFields( relationH.get(), lst );
|
|
CSLDestroy( lst );
|
|
}
|
|
|
|
// set type
|
|
switch ( relationship.strength() )
|
|
{
|
|
case Qgis::RelationshipStrength::Association:
|
|
GDALRelationshipSetType( relationH.get(), GDALRelationshipType::GRT_ASSOCIATION );
|
|
break;
|
|
|
|
case Qgis::RelationshipStrength::Composition:
|
|
GDALRelationshipSetType( relationH.get(), GDALRelationshipType::GRT_COMPOSITE );
|
|
break;
|
|
}
|
|
|
|
// set labels
|
|
if ( !relationship.forwardPathLabel().isEmpty() )
|
|
GDALRelationshipSetForwardPathLabel( relationH.get(), relationship.forwardPathLabel().toLocal8Bit().constData() );
|
|
if ( !relationship.backwardPathLabel().isEmpty() )
|
|
GDALRelationshipSetBackwardPathLabel( relationH.get(), relationship.backwardPathLabel().toLocal8Bit().constData() );
|
|
|
|
// set table type
|
|
if ( !relationship.relatedTableType().isEmpty() )
|
|
GDALRelationshipSetRelatedTableType( relationH.get(), relationship.relatedTableType().toLocal8Bit().constData() );
|
|
|
|
return relationH;
|
|
}
|
|
#endif
|