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