QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsarcgisrestutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsarcgisrestutils.cpp
3  ----------------------
4  begin : Nov 25, 2015
5  copyright : (C) 2015 by Sandro Mani
6  email : [email protected]
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 "qgsarcgisrestutils.h"
17 #include "qgsfields.h"
18 #include "qgslogger.h"
19 #include "qgsrectangle.h"
20 #include "qgspallabeling.h"
21 #include "qgssymbol.h"
22 #include "qgssymbollayer.h"
23 #include "qgslinesymbollayer.h"
24 #include "qgsfillsymbollayer.h"
25 #include "qgsrenderer.h"
26 #include "qgsrulebasedlabeling.h"
29 #include "qgsvectorlayerlabeling.h"
30 #include "qgscircularstring.h"
31 #include "qgsmulticurve.h"
32 #include "qgspolygon.h"
33 #include "qgslinestring.h"
34 #include "qgscurve.h"
35 #include "qgsgeometryengine.h"
36 #include "qgsmultisurface.h"
37 #include "qgsmultipoint.h"
38 #include "qgsmarkersymbol.h"
39 #include "qgslinesymbol.h"
40 #include "qgsfillsymbol.h"
41 
42 #include <QRegularExpression>
43 
44 QVariant::Type QgsArcGisRestUtils::convertFieldType( const QString &esriFieldType )
45 {
46  if ( esriFieldType == QLatin1String( "esriFieldTypeInteger" ) )
47  return QVariant::LongLong;
48  if ( esriFieldType == QLatin1String( "esriFieldTypeSmallInteger" ) )
49  return QVariant::Int;
50  if ( esriFieldType == QLatin1String( "esriFieldTypeDouble" ) )
51  return QVariant::Double;
52  if ( esriFieldType == QLatin1String( "esriFieldTypeSingle" ) )
53  return QVariant::Double;
54  if ( esriFieldType == QLatin1String( "esriFieldTypeString" ) )
55  return QVariant::String;
56  if ( esriFieldType == QLatin1String( "esriFieldTypeDate" ) )
57  return QVariant::DateTime;
58  if ( esriFieldType == QLatin1String( "esriFieldTypeGeometry" ) )
59  return QVariant::Invalid; // Geometry column should not appear as field
60  if ( esriFieldType == QLatin1String( "esriFieldTypeOID" ) )
61  return QVariant::LongLong;
62  if ( esriFieldType == QLatin1String( "esriFieldTypeBlob" ) )
63  return QVariant::ByteArray;
64  if ( esriFieldType == QLatin1String( "esriFieldTypeGlobalID" ) )
65  return QVariant::String;
66  if ( esriFieldType == QLatin1String( "esriFieldTypeRaster" ) )
67  return QVariant::ByteArray;
68  if ( esriFieldType == QLatin1String( "esriFieldTypeGUID" ) )
69  return QVariant::String;
70  if ( esriFieldType == QLatin1String( "esriFieldTypeXML" ) )
71  return QVariant::String;
72  return QVariant::Invalid;
73 }
74 
76 {
77  // http://resources.arcgis.com/en/help/arcobjects-cpp/componenthelp/index.html#//000w0000001p000000
78  if ( esriGeometryType == QLatin1String( "esriGeometryNull" ) )
79  return QgsWkbTypes::Unknown;
80  else if ( esriGeometryType == QLatin1String( "esriGeometryPoint" ) )
81  return QgsWkbTypes::Point;
82  else if ( esriGeometryType == QLatin1String( "esriGeometryMultipoint" ) )
84  else if ( esriGeometryType == QLatin1String( "esriGeometryPolyline" ) )
86  else if ( esriGeometryType == QLatin1String( "esriGeometryPolygon" ) )
88  else if ( esriGeometryType == QLatin1String( "esriGeometryEnvelope" ) )
89  return QgsWkbTypes::Polygon;
90  // Unsupported (either by qgis, or format unspecified by the specification)
91  // esriGeometryCircularArc
92  // esriGeometryEllipticArc
93  // esriGeometryBezier3Curve
94  // esriGeometryPath
95  // esriGeometryRing
96  // esriGeometryLine
97  // esriGeometryAny
98  // esriGeometryMultiPatch
99  // esriGeometryTriangleStrip
100  // esriGeometryTriangleFan
101  // esriGeometryRay
102  // esriGeometrySphere
103  // esriGeometryTriangles
104  // esriGeometryBag
105  return QgsWkbTypes::Unknown;
106 }
107 
108 std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertPoint( const QVariantList &coordList, QgsWkbTypes::Type pointType )
109 {
110  int nCoords = coordList.size();
111  if ( nCoords < 2 )
112  return nullptr;
113  bool xok = false, yok = false;
114  double x = coordList[0].toDouble( &xok );
115  double y = coordList[1].toDouble( &yok );
116  if ( !xok || !yok )
117  return nullptr;
118  double z = nCoords >= 3 ? coordList[2].toDouble() : 0;
119  double m = nCoords >= 4 ? coordList[3].toDouble() : 0;
120  return std::make_unique< QgsPoint >( pointType, x, y, z, m );
121 }
122 
123 std::unique_ptr< QgsCircularString > QgsArcGisRestUtils::convertCircularString( const QVariantMap &curveData, QgsWkbTypes::Type pointType, const QgsPoint &startPoint )
124 {
125  const QVariantList coordsList = curveData[QStringLiteral( "c" )].toList();
126  if ( coordsList.isEmpty() )
127  return nullptr;
128  QVector<QgsPoint> points;
129  points.append( startPoint );
130  for ( const QVariant &coordData : coordsList )
131  {
132  std::unique_ptr< QgsPoint > point( convertPoint( coordData.toList(), pointType ) );
133  if ( !point )
134  {
135  return nullptr;
136  }
137  points.append( *point );
138  }
139  std::unique_ptr< QgsCircularString > curve = std::make_unique< QgsCircularString> ();
140  curve->setPoints( points );
141  return curve;
142 }
143 
144 std::unique_ptr< QgsCompoundCurve > QgsArcGisRestUtils::convertCompoundCurve( const QVariantList &curvesList, QgsWkbTypes::Type pointType )
145 {
146  // [[6,3],[5,3],{"b":[[3,2],[6,1],[2,4]]},[1,2],{"c": [[3,3],[1,4]]}]
147  std::unique_ptr< QgsCompoundCurve > compoundCurve = std::make_unique< QgsCompoundCurve >();
148  QgsLineString *lineString = new QgsLineString();
149  compoundCurve->addCurve( lineString );
150  for ( const QVariant &curveData : curvesList )
151  {
152  if ( curveData.type() == QVariant::List )
153  {
154  std::unique_ptr< QgsPoint > point( convertPoint( curveData.toList(), pointType ) );
155  if ( !point )
156  {
157  return nullptr;
158  }
159  lineString->addVertex( *point );
160  }
161  else if ( curveData.type() == QVariant::Map )
162  {
163  // The last point of the linestring is the start point of this circular string
164  std::unique_ptr< QgsCircularString > circularString( convertCircularString( curveData.toMap(), pointType, lineString->endPoint() ) );
165  if ( !circularString )
166  {
167  return nullptr;
168  }
169 
170  // If the previous curve had less than two points, remove it
171  if ( compoundCurve->curveAt( compoundCurve->nCurves() - 1 )->nCoordinates() < 2 )
172  compoundCurve->removeCurve( compoundCurve->nCurves() - 1 );
173 
174  const QgsPoint endPointCircularString = circularString->endPoint();
175  compoundCurve->addCurve( circularString.release() );
176 
177  // Prepare a new line string
178  lineString = new QgsLineString;
179  compoundCurve->addCurve( lineString );
180  lineString->addVertex( endPointCircularString );
181  }
182  }
183  return compoundCurve;
184 }
185 
186 std::unique_ptr< QgsPoint > QgsArcGisRestUtils::convertGeometryPoint( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
187 {
188  // {"x" : <x>, "y" : <y>, "z" : <z>, "m" : <m>}
189  bool xok = false, yok = false;
190  double x = geometryData[QStringLiteral( "x" )].toDouble( &xok );
191  double y = geometryData[QStringLiteral( "y" )].toDouble( &yok );
192  if ( !xok || !yok )
193  return nullptr;
194  double z = geometryData[QStringLiteral( "z" )].toDouble();
195  double m = geometryData[QStringLiteral( "m" )].toDouble();
196  return std::make_unique< QgsPoint >( pointType, x, y, z, m );
197 }
198 
199 std::unique_ptr< QgsMultiPoint > QgsArcGisRestUtils::convertMultiPoint( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
200 {
201  // {"points" : [[ <x1>, <y1>, <z1>, <m1> ] , [ <x2>, <y2>, <z2>, <m2> ], ... ]}
202  const QVariantList coordsList = geometryData[QStringLiteral( "points" )].toList();
203 
204  std::unique_ptr< QgsMultiPoint > multiPoint = std::make_unique< QgsMultiPoint >();
205  multiPoint->reserve( coordsList.size() );
206  for ( const QVariant &coordData : coordsList )
207  {
208  const QVariantList coordList = coordData.toList();
209  std::unique_ptr< QgsPoint > p = convertPoint( coordList, pointType );
210  if ( !p )
211  {
212  continue;
213  }
214  multiPoint->addGeometry( p.release() );
215  }
216 
217  // second chance -- sometimes layers are reported as multipoint but features have single
218  // point geometries. Silently handle this and upgrade to multipoint.
219  std::unique_ptr< QgsPoint > p = convertGeometryPoint( geometryData, pointType );
220  if ( p )
221  multiPoint->addGeometry( p.release() );
222 
223  if ( multiPoint->numGeometries() == 0 )
224  {
225  // didn't find any points, so reset geometry to null
226  multiPoint.reset();
227  }
228  return multiPoint;
229 }
230 
231 std::unique_ptr< QgsMultiCurve > QgsArcGisRestUtils::convertGeometryPolyline( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
232 {
233  // {"curvePaths": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
234  QVariantList pathsList;
235  if ( geometryData[QStringLiteral( "paths" )].isValid() )
236  pathsList = geometryData[QStringLiteral( "paths" )].toList();
237  else if ( geometryData[QStringLiteral( "curvePaths" )].isValid() )
238  pathsList = geometryData[QStringLiteral( "curvePaths" )].toList();
239  if ( pathsList.isEmpty() )
240  return nullptr;
241  std::unique_ptr< QgsMultiCurve > multiCurve = std::make_unique< QgsMultiCurve >();
242  multiCurve->reserve( pathsList.size() );
243  for ( const QVariant &pathData : std::as_const( pathsList ) )
244  {
245  std::unique_ptr< QgsCompoundCurve > curve = convertCompoundCurve( pathData.toList(), pointType );
246  if ( !curve )
247  {
248  return nullptr;
249  }
250  multiCurve->addGeometry( curve.release() );
251  }
252  return multiCurve;
253 }
254 
255 std::unique_ptr< QgsMultiSurface > QgsArcGisRestUtils::convertGeometryPolygon( const QVariantMap &geometryData, QgsWkbTypes::Type pointType )
256 {
257  // {"curveRings": [[[0,0], {"c": [[3,3],[1,4]]} ]]}
258  QVariantList ringsList;
259  if ( geometryData[QStringLiteral( "rings" )].isValid() )
260  ringsList = geometryData[QStringLiteral( "rings" )].toList();
261  else if ( geometryData[QStringLiteral( "ringPaths" )].isValid() )
262  ringsList = geometryData[QStringLiteral( "ringPaths" )].toList();
263  if ( ringsList.isEmpty() )
264  return nullptr;
265 
266  QList< QgsCompoundCurve * > curves;
267  for ( int i = 0, n = ringsList.size(); i < n; ++i )
268  {
269  std::unique_ptr< QgsCompoundCurve > curve = convertCompoundCurve( ringsList[i].toList(), pointType );
270  if ( !curve )
271  {
272  continue;
273  }
274  curves.append( curve.release() );
275  }
276  if ( curves.count() == 0 )
277  return nullptr;
278 
279  std::sort( curves.begin(), curves.end(), []( const QgsCompoundCurve * a, const QgsCompoundCurve * b )->bool{ double a_area = 0.0; double b_area = 0.0; a->sumUpArea( a_area ); b->sumUpArea( b_area ); return std::abs( a_area ) > std::abs( b_area ); } );
280  std::unique_ptr< QgsMultiSurface > result = std::make_unique< QgsMultiSurface >();
281  result->reserve( curves.size() );
282  while ( !curves.isEmpty() )
283  {
284  QgsCompoundCurve *exterior = curves.takeFirst();
285  QgsCurvePolygon *newPolygon = new QgsCurvePolygon();
286  newPolygon->setExteriorRing( exterior );
287  std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( newPolygon ) );
288  engine->prepareGeometry();
289 
290  QMutableListIterator< QgsCompoundCurve * > it( curves );
291  while ( it.hasNext() )
292  {
293  QgsCompoundCurve *curve = it.next();
294  QgsRectangle boundingBox = newPolygon->boundingBox();
295  if ( boundingBox.intersects( curve->boundingBox() ) )
296  {
297  QgsPoint point = curve->startPoint();
298  if ( engine->contains( &point ) )
299  {
300  newPolygon->addInteriorRing( curve );
301  it.remove();
302  engine.reset( QgsGeometry::createGeometryEngine( newPolygon ) );
303  engine->prepareGeometry();
304  }
305  }
306  }
307  result->addGeometry( newPolygon );
308  }
309  if ( result->numGeometries() == 0 )
310  return nullptr;
311 
312  return result;
313 }
314 
315 std::unique_ptr< QgsPolygon > QgsArcGisRestUtils::convertEnvelope( const QVariantMap &geometryData )
316 {
317  // {"xmin" : -109.55, "ymin" : 25.76, "xmax" : -86.39, "ymax" : 49.94}
318  bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
319  double xmin = geometryData[QStringLiteral( "xmin" )].toDouble( &xminOk );
320  double ymin = geometryData[QStringLiteral( "ymin" )].toDouble( &yminOk );
321  double xmax = geometryData[QStringLiteral( "xmax" )].toDouble( &xmaxOk );
322  double ymax = geometryData[QStringLiteral( "ymax" )].toDouble( &ymaxOk );
323  if ( !xminOk || !yminOk || !xmaxOk || !ymaxOk )
324  return nullptr;
325  std::unique_ptr< QgsLineString > ext = std::make_unique< QgsLineString> ();
326  ext->addVertex( QgsPoint( xmin, ymin ) );
327  ext->addVertex( QgsPoint( xmax, ymin ) );
328  ext->addVertex( QgsPoint( xmax, ymax ) );
329  ext->addVertex( QgsPoint( xmin, ymax ) );
330  ext->addVertex( QgsPoint( xmin, ymin ) );
331  std::unique_ptr< QgsPolygon > poly = std::make_unique< QgsPolygon >();
332  poly->setExteriorRing( ext.release() );
333  return poly;
334 }
335 
336 QgsAbstractGeometry *QgsArcGisRestUtils::convertGeometry( const QVariantMap &geometryData, const QString &esriGeometryType, bool readM, bool readZ, QgsCoordinateReferenceSystem *crs )
337 {
338  QgsWkbTypes::Type pointType = QgsWkbTypes::zmType( QgsWkbTypes::Point, readZ, readM );
339  if ( crs )
340  {
341  *crs = convertSpatialReference( geometryData[QStringLiteral( "spatialReference" )].toMap() );
342  }
343 
344  // http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Geometry_Objects/02r3000000n1000000/
345  if ( esriGeometryType == QLatin1String( "esriGeometryNull" ) )
346  return nullptr;
347  else if ( esriGeometryType == QLatin1String( "esriGeometryPoint" ) )
348  return convertGeometryPoint( geometryData, pointType ).release();
349  else if ( esriGeometryType == QLatin1String( "esriGeometryMultipoint" ) )
350  return convertMultiPoint( geometryData, pointType ).release();
351  else if ( esriGeometryType == QLatin1String( "esriGeometryPolyline" ) )
352  return convertGeometryPolyline( geometryData, pointType ).release();
353  else if ( esriGeometryType == QLatin1String( "esriGeometryPolygon" ) )
354  return convertGeometryPolygon( geometryData, pointType ).release();
355  else if ( esriGeometryType == QLatin1String( "esriGeometryEnvelope" ) )
356  return convertEnvelope( geometryData ).release();
357  // Unsupported (either by qgis, or format unspecified by the specification)
358  // esriGeometryCircularArc
359  // esriGeometryEllipticArc
360  // esriGeometryBezier3Curve
361  // esriGeometryPath
362  // esriGeometryRing
363  // esriGeometryLine
364  // esriGeometryAny
365  // esriGeometryMultiPatch
366  // esriGeometryTriangleStrip
367  // esriGeometryTriangleFan
368  // esriGeometryRay
369  // esriGeometrySphere
370  // esriGeometryTriangles
371  // esriGeometryBag
372  return nullptr;
373 }
374 
376 {
377  QString spatialReference = spatialReferenceMap[QStringLiteral( "latestWkid" )].toString();
378  if ( spatialReference.isEmpty() )
379  spatialReference = spatialReferenceMap[QStringLiteral( "wkid" )].toString();
380  if ( spatialReference.isEmpty() )
381  spatialReference = spatialReferenceMap[QStringLiteral( "wkt" )].toString();
382  else
383  spatialReference = QStringLiteral( "EPSG:%1" ).arg( spatialReference );
385  crs.createFromString( spatialReference );
386  if ( !crs.isValid() )
387  {
388  // If not spatial reference, just use WGS84
389  crs.createFromString( QStringLiteral( "EPSG:4326" ) );
390  }
391  return crs;
392 }
393 
394 QgsSymbol *QgsArcGisRestUtils::convertSymbol( const QVariantMap &symbolData )
395 {
396  const QString type = symbolData.value( QStringLiteral( "type" ) ).toString();
397  if ( type == QLatin1String( "esriSMS" ) )
398  {
399  // marker symbol
400  return parseEsriMarkerSymbolJson( symbolData ).release();
401  }
402  else if ( type == QLatin1String( "esriSLS" ) )
403  {
404  // line symbol
405  return parseEsriLineSymbolJson( symbolData ).release();
406  }
407  else if ( type == QLatin1String( "esriSFS" ) )
408  {
409  // fill symbol
410  return parseEsriFillSymbolJson( symbolData ).release();
411  }
412  else if ( type == QLatin1String( "esriPFS" ) )
413  {
414  return parseEsriPictureFillSymbolJson( symbolData ).release();
415  }
416  else if ( type == QLatin1String( "esriPMS" ) )
417  {
418  // picture marker
419  return parseEsriPictureMarkerSymbolJson( symbolData ).release();
420  }
421  else if ( type == QLatin1String( "esriTS" ) )
422  {
423  // text symbol - not supported
424  return nullptr;
425  }
426  return nullptr;
427 }
428 
429 std::unique_ptr<QgsLineSymbol> QgsArcGisRestUtils::parseEsriLineSymbolJson( const QVariantMap &symbolData )
430 {
431  QColor lineColor = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
432  if ( !lineColor.isValid() )
433  return nullptr;
434 
435  bool ok = false;
436  double widthInPoints = symbolData.value( QStringLiteral( "width" ) ).toDouble( &ok );
437  if ( !ok )
438  return nullptr;
439 
440  QgsSymbolLayerList layers;
441  Qt::PenStyle penStyle = convertLineStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
442  std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
443  lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
444  layers.append( lineLayer.release() );
445 
446  std::unique_ptr< QgsLineSymbol > symbol = std::make_unique< QgsLineSymbol >( layers );
447  return symbol;
448 }
449 
450 std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriFillSymbolJson( const QVariantMap &symbolData )
451 {
452  QColor fillColor = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
453  Qt::BrushStyle brushStyle = convertFillStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
454 
455  const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
456  QColor lineColor = convertColor( outlineData.value( QStringLiteral( "color" ) ) );
457  Qt::PenStyle penStyle = convertLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
458  bool ok = false;
459  double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
460 
461  QgsSymbolLayerList layers;
462  std::unique_ptr< QgsSimpleFillSymbolLayer > fillLayer = std::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
463  fillLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
464  layers.append( fillLayer.release() );
465 
466  std::unique_ptr< QgsFillSymbol > symbol = std::make_unique< QgsFillSymbol >( layers );
467  return symbol;
468 }
469 
470 std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriPictureFillSymbolJson( const QVariantMap &symbolData )
471 {
472  bool ok = false;
473 
474  double widthInPixels = symbolData.value( QStringLiteral( "width" ) ).toInt( &ok );
475  if ( !ok )
476  return nullptr;
477 
478  const double xScale = symbolData.value( QStringLiteral( "xscale" ) ).toDouble( &ok );
479  if ( !qgsDoubleNear( xScale, 0.0 ) )
480  widthInPixels *= xScale;
481 
482  const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
483  double angleCW = 0;
484  if ( ok )
485  angleCW = -angleCCW;
486 
487  const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
488  const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
489 
490  QString symbolPath( symbolData.value( QStringLiteral( "imageData" ) ).toString() );
491  symbolPath.prepend( QLatin1String( "base64:" ) );
492 
493  QgsSymbolLayerList layers;
494  std::unique_ptr< QgsRasterFillSymbolLayer > fillLayer = std::make_unique< QgsRasterFillSymbolLayer >( symbolPath );
495  fillLayer->setWidth( widthInPixels );
496  fillLayer->setAngle( angleCW );
497  fillLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
498  fillLayer->setOffset( QPointF( xOffset, yOffset ) );
499  fillLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
500  layers.append( fillLayer.release() );
501 
502  const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
503  QColor lineColor = convertColor( outlineData.value( QStringLiteral( "color" ) ) );
504  Qt::PenStyle penStyle = convertLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
505  double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
506 
507  std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = std::make_unique< QgsSimpleLineSymbolLayer >( lineColor, penWidthInPoints, penStyle );
508  lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
509  layers.append( lineLayer.release() );
510 
511  std::unique_ptr< QgsFillSymbol > symbol = std::make_unique< QgsFillSymbol >( layers );
512  return symbol;
513 }
514 
515 QgsSimpleMarkerSymbolLayerBase::Shape QgsArcGisRestUtils::parseEsriMarkerShape( const QString &style )
516 {
517  if ( style == QLatin1String( "esriSMSCircle" ) )
519  else if ( style == QLatin1String( "esriSMSCross" ) )
521  else if ( style == QLatin1String( "esriSMSDiamond" ) )
523  else if ( style == QLatin1String( "esriSMSSquare" ) )
525  else if ( style == QLatin1String( "esriSMSX" ) )
527  else if ( style == QLatin1String( "esriSMSTriangle" ) )
529  else
531 }
532 
533 std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
534 {
535  QColor fillColor = convertColor( symbolData.value( QStringLiteral( "color" ) ) );
536  bool ok = false;
537  const double sizeInPoints = symbolData.value( QStringLiteral( "size" ) ).toDouble( &ok );
538  if ( !ok )
539  return nullptr;
540  const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
541  double angleCW = 0;
542  if ( ok )
543  angleCW = -angleCCW;
544 
545  QgsSimpleMarkerSymbolLayerBase::Shape shape = parseEsriMarkerShape( symbolData.value( QStringLiteral( "style" ) ).toString() );
546 
547  const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
548  const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
549 
550  const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
551  QColor lineColor = convertColor( outlineData.value( QStringLiteral( "color" ) ) );
552  Qt::PenStyle penStyle = convertLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
553  double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );
554 
555  QgsSymbolLayerList layers;
556  std::unique_ptr< QgsSimpleMarkerSymbolLayer > markerLayer = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, Qgis::ScaleMethod::ScaleArea, fillColor, lineColor );
557  markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
558  markerLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
559  markerLayer->setStrokeStyle( penStyle );
560  markerLayer->setStrokeWidth( penWidthInPoints );
561  markerLayer->setOffset( QPointF( xOffset, yOffset ) );
562  markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
563  layers.append( markerLayer.release() );
564 
565  std::unique_ptr< QgsMarkerSymbol > symbol = std::make_unique< QgsMarkerSymbol >( layers );
566  return symbol;
567 }
568 
569 std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData )
570 {
571  bool ok = false;
572  const double widthInPixels = symbolData.value( QStringLiteral( "width" ) ).toInt( &ok );
573  if ( !ok )
574  return nullptr;
575  const double heightInPixels = symbolData.value( QStringLiteral( "height" ) ).toInt( &ok );
576  if ( !ok )
577  return nullptr;
578 
579  const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
580  double angleCW = 0;
581  if ( ok )
582  angleCW = -angleCCW;
583 
584  const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
585  const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();
586 
587  //const QString contentType = symbolData.value( QStringLiteral( "contentType" ) ).toString();
588 
589  QString symbolPath( symbolData.value( QStringLiteral( "imageData" ) ).toString() );
590  symbolPath.prepend( QLatin1String( "base64:" ) );
591 
592  QgsSymbolLayerList layers;
593  std::unique_ptr< QgsRasterMarkerSymbolLayer > markerLayer = std::make_unique< QgsRasterMarkerSymbolLayer >( symbolPath, widthInPixels, angleCW, Qgis::ScaleMethod::ScaleArea );
594  markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
595 
596  // only change the default aspect ratio if the server height setting requires this
597  if ( !qgsDoubleNear( static_cast< double >( heightInPixels ) / widthInPixels, markerLayer->defaultAspectRatio() ) )
598  markerLayer->setFixedAspectRatio( static_cast< double >( heightInPixels ) / widthInPixels );
599 
600  markerLayer->setOffset( QPointF( xOffset, yOffset ) );
601  markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
602  layers.append( markerLayer.release() );
603 
604  std::unique_ptr< QgsMarkerSymbol > symbol = std::make_unique< QgsMarkerSymbol >( layers );
605  return symbol;
606 }
607 
609 {
610  if ( labelingData.empty() )
611  return nullptr;
612 
613  QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings(), 0, 0, QString(), QString(), false );
614  root->setActive( true );
615 
616  int i = 1;
617  for ( const QVariant &lbl : labelingData )
618  {
619  const QVariantMap labeling = lbl.toMap();
620 
621  QgsPalLayerSettings *settings = new QgsPalLayerSettings();
622  QgsTextFormat format;
623 
624  const QString placement = labeling.value( QStringLiteral( "labelPlacement" ) ).toString();
625  if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveCenter" ) )
626  {
629  }
630  else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowCenter" ) )
631  {
634  }
635  else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterCenter" ) )
636  {
639  }
640  else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveLeft" ) )
641  {
644  }
645  else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowLeft" ) )
646  {
649  }
650  else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterLeft" ) )
651  {
654  }
655  else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveRight" ) )
656  {
659  }
660  else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowRight" ) )
661  {
664  }
665  else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterRight" ) )
666  {
669  }
670  else if ( placement == QLatin1String( "esriServerLinePlacementAboveAfter" ) ||
671  placement == QLatin1String( "esriServerLinePlacementAboveStart" ) ||
672  placement == QLatin1String( "esriServerLinePlacementAboveAlong" ) )
673  {
675  settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::MapOrientation );
676  }
677  else if ( placement == QLatin1String( "esriServerLinePlacementBelowAfter" ) ||
678  placement == QLatin1String( "esriServerLinePlacementBelowStart" ) ||
679  placement == QLatin1String( "esriServerLinePlacementBelowAlong" ) )
680  {
682  settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::BelowLine | QgsLabeling::LinePlacementFlag::MapOrientation );
683  }
684  else if ( placement == QLatin1String( "esriServerLinePlacementCenterAfter" ) ||
685  placement == QLatin1String( "esriServerLinePlacementCenterStart" ) ||
686  placement == QLatin1String( "esriServerLinePlacementCenterAlong" ) )
687  {
689  settings->lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::MapOrientation );
690  }
691  else if ( placement == QLatin1String( "esriServerPolygonPlacementAlwaysHorizontal" ) )
692  {
694  }
695 
696  const double minScale = labeling.value( QStringLiteral( "minScale" ) ).toDouble();
697  const double maxScale = labeling.value( QStringLiteral( "maxScale" ) ).toDouble();
698 
699  QVariantMap symbol = labeling.value( QStringLiteral( "symbol" ) ).toMap();
700  format.setColor( convertColor( symbol.value( QStringLiteral( "color" ) ) ) );
701  const double haloSize = symbol.value( QStringLiteral( "haloSize" ) ).toDouble();
702  if ( !qgsDoubleNear( haloSize, 0.0 ) )
703  {
704  QgsTextBufferSettings buffer;
705  buffer.setEnabled( true );
706  buffer.setSize( haloSize );
708  buffer.setColor( convertColor( symbol.value( QStringLiteral( "haloColor" ) ) ) );
709  format.setBuffer( buffer );
710  }
711 
712  const QString fontFamily = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "family" ) ).toString();
713  const QString fontStyle = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "style" ) ).toString();
714  const QString fontWeight = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "weight" ) ).toString();
715  const int fontSize = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "size" ) ).toInt();
716  QFont font( fontFamily, fontSize );
717  font.setStyleName( fontStyle );
718  font.setWeight( fontWeight == QLatin1String( "bold" ) ? QFont::Bold : QFont::Normal );
719 
720  format.setFont( font );
721  format.setSize( fontSize );
723 
724  settings->setFormat( format );
725 
726  QString where = labeling.value( QStringLiteral( "where" ) ).toString();
727  QgsExpression exp( where );
728  // If the where clause isn't parsed as valid, don't use its
729  if ( !exp.isValid() )
730  where.clear();
731 
732  settings->fieldName = convertLabelingExpression( labeling.value( QStringLiteral( "labelExpression" ) ).toString() );
733  settings->isExpression = true;
734 
735  QgsRuleBasedLabeling::Rule *child = new QgsRuleBasedLabeling::Rule( settings, maxScale, minScale, where, QObject::tr( "ASF label %1" ).arg( i++ ), false );
736  child->setActive( true );
737  root->appendChild( child );
738  }
739 
740  return new QgsRuleBasedLabeling( root );
741 }
742 
743 QgsFeatureRenderer *QgsArcGisRestUtils::convertRenderer( const QVariantMap &rendererData )
744 {
745  const QString type = rendererData.value( QStringLiteral( "type" ) ).toString();
746  if ( type == QLatin1String( "simple" ) )
747  {
748  const QVariantMap symbolProps = rendererData.value( QStringLiteral( "symbol" ) ).toMap();
749  std::unique_ptr< QgsSymbol > symbol( convertSymbol( symbolProps ) );
750  if ( symbol )
751  return new QgsSingleSymbolRenderer( symbol.release() );
752  else
753  return nullptr;
754  }
755  else if ( type == QLatin1String( "uniqueValue" ) )
756  {
757  const QString field1 = rendererData.value( QStringLiteral( "field1" ) ).toString();
758  const QString field2 = rendererData.value( QStringLiteral( "field2" ) ).toString();
759  const QString field3 = rendererData.value( QStringLiteral( "field3" ) ).toString();
760  QString attribute;
761  if ( !field2.isEmpty() || !field3.isEmpty() )
762  {
763  const QString delimiter = rendererData.value( QStringLiteral( "fieldDelimiter" ) ).toString();
764  if ( !field3.isEmpty() )
765  {
766  attribute = QStringLiteral( "concat(\"%1\",'%2',\"%3\",'%4',\"%5\")" ).arg( field1, delimiter, field2, delimiter, field3 );
767  }
768  else
769  {
770  attribute = QStringLiteral( "concat(\"%1\",'%2',\"%3\")" ).arg( field1, delimiter, field2 );
771  }
772  }
773  else
774  {
775  attribute = field1;
776  }
777 
778  const QVariantList categories = rendererData.value( QStringLiteral( "uniqueValueInfos" ) ).toList();
779  QgsCategoryList categoryList;
780  for ( const QVariant &category : categories )
781  {
782  const QVariantMap categoryData = category.toMap();
783  const QString value = categoryData.value( QStringLiteral( "value" ) ).toString();
784  const QString label = categoryData.value( QStringLiteral( "label" ) ).toString();
785  std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( categoryData.value( QStringLiteral( "symbol" ) ).toMap() ) );
786  if ( symbol )
787  {
788  categoryList.append( QgsRendererCategory( value, symbol.release(), label ) );
789  }
790  }
791 
792  std::unique_ptr< QgsSymbol > defaultSymbol( convertSymbol( rendererData.value( QStringLiteral( "defaultSymbol" ) ).toMap() ) );
793  if ( defaultSymbol )
794  {
795  categoryList.append( QgsRendererCategory( QVariant(), defaultSymbol.release(), rendererData.value( QStringLiteral( "defaultLabel" ) ).toString() ) );
796  }
797 
798  if ( categoryList.empty() )
799  return nullptr;
800 
801  return new QgsCategorizedSymbolRenderer( attribute, categoryList );
802  }
803  else if ( type == QLatin1String( "classBreaks" ) )
804  {
805  // currently unsupported
806  return nullptr;
807  }
808  else if ( type == QLatin1String( "heatmap" ) )
809  {
810  // currently unsupported
811  return nullptr;
812  }
813  else if ( type == QLatin1String( "vectorField" ) )
814  {
815  // currently unsupported
816  return nullptr;
817  }
818  return nullptr;
819 }
820 
821 QString QgsArcGisRestUtils::convertLabelingExpression( const QString &string )
822 {
823  QString expression = string;
824 
825  // Replace a few ArcGIS token to QGIS equivalents
826  expression = expression.replace( QRegularExpression( "(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)CONCAT(\\s|$)" ), QStringLiteral( "\\4||\\5" ) );
827  expression = expression.replace( QRegularExpression( "(?=([^\"\\\\]*(\\\\.|\"([^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^\"]*$)(\\s|^)NEWLINE(\\s|$)" ), QStringLiteral( "\\4'\\n'\\5" ) );
828 
829  // ArcGIS's double quotes are single quotes in QGIS
830  expression = expression.replace( QRegularExpression( "\"(.*?(?<!\\\\))\"" ), QStringLiteral( "'\\1'" ) );
831  expression = expression.replace( QRegularExpression( "\\\\\"" ), QStringLiteral( "\"" ) );
832 
833  // ArcGIS's square brakets are double quotes in QGIS
834  expression = expression.replace( QRegularExpression( "\\[([^]]*)\\]" ), QStringLiteral( "\"\\1\"" ) );
835 
836  return expression;
837 }
838 
839 QColor QgsArcGisRestUtils::convertColor( const QVariant &colorData )
840 {
841  const QVariantList colorParts = colorData.toList();
842  if ( colorParts.count() < 4 )
843  return QColor();
844 
845  int red = colorParts.at( 0 ).toInt();
846  int green = colorParts.at( 1 ).toInt();
847  int blue = colorParts.at( 2 ).toInt();
848  int alpha = colorParts.at( 3 ).toInt();
849  return QColor( red, green, blue, alpha );
850 }
851 
852 Qt::PenStyle QgsArcGisRestUtils::convertLineStyle( const QString &style )
853 {
854  if ( style == QLatin1String( "esriSLSSolid" ) )
855  return Qt::SolidLine;
856  else if ( style == QLatin1String( "esriSLSDash" ) )
857  return Qt::DashLine;
858  else if ( style == QLatin1String( "esriSLSDashDot" ) )
859  return Qt::DashDotLine;
860  else if ( style == QLatin1String( "esriSLSDashDotDot" ) )
861  return Qt::DashDotDotLine;
862  else if ( style == QLatin1String( "esriSLSDot" ) )
863  return Qt::DotLine;
864  else if ( style == QLatin1String( "esriSLSNull" ) )
865  return Qt::NoPen;
866  else
867  return Qt::SolidLine;
868 }
869 
870 Qt::BrushStyle QgsArcGisRestUtils::convertFillStyle( const QString &style )
871 {
872  if ( style == QLatin1String( "esriSFSBackwardDiagonal" ) )
873  return Qt::BDiagPattern;
874  else if ( style == QLatin1String( "esriSFSCross" ) )
875  return Qt::CrossPattern;
876  else if ( style == QLatin1String( "esriSFSDiagonalCross" ) )
877  return Qt::DiagCrossPattern;
878  else if ( style == QLatin1String( "esriSFSForwardDiagonal" ) )
879  return Qt::FDiagPattern;
880  else if ( style == QLatin1String( "esriSFSHorizontal" ) )
881  return Qt::HorPattern;
882  else if ( style == QLatin1String( "esriSFSNull" ) )
883  return Qt::NoBrush;
884  else if ( style == QLatin1String( "esriSFSSolid" ) )
885  return Qt::SolidPattern;
886  else if ( style == QLatin1String( "esriSFSVertical" ) )
887  return Qt::VerPattern;
888  else
889  return Qt::SolidPattern;
890 }
891 
892 QDateTime QgsArcGisRestUtils::convertDateTime( const QVariant &value )
893 {
894  if ( value.isNull() )
895  return QDateTime();
896  bool ok = false;
897  QDateTime dt = QDateTime::fromMSecsSinceEpoch( value.toLongLong( &ok ) );
898  if ( !ok )
899  {
900  QgsDebugMsg( QStringLiteral( "Invalid value %1 for datetime" ).arg( value.toString() ) );
901  return QDateTime();
902  }
903  else
904  return dt;
905 }
@ ScaleArea
Calculate scale by the area.
Abstract base class for all geometries.
Abstract base class - its implementations define different approaches to the labeling of a vector lay...
static QgsCoordinateReferenceSystem convertSpatialReference(const QVariantMap &spatialReferenceMap)
Converts a spatial reference JSON definition to a QgsCoordinateReferenceSystem value.
static QDateTime convertDateTime(const QVariant &value)
Converts a date time value to a QDateTime.
static QString convertLabelingExpression(const QString &string)
Converts an ESRI labeling expression to a QGIS expression string.
static QgsSymbol * convertSymbol(const QVariantMap &definition)
Converts a symbol JSON definition to a QgsSymbol.
static QgsAbstractGeometry * convertGeometry(const QVariantMap &geometry, const QString &esriGeometryType, bool hasM, bool hasZ, QgsCoordinateReferenceSystem *crs=nullptr)
Converts an ESRI REST geometry JSON definition to a QgsAbstractGeometry.
static Qt::PenStyle convertLineStyle(const QString &style)
Converts an ESRI line style to a Qt pen style.
static QgsFeatureRenderer * convertRenderer(const QVariantMap &rendererData)
Converts renderer JSON data to an equivalent QgsFeatureRenderer.
static Qt::BrushStyle convertFillStyle(const QString &style)
Converts an ESRI fill style to a Qt brush style.
static QVariant::Type convertFieldType(const QString &type)
Converts an ESRI REST field type to a QVariant type.
static QgsWkbTypes::Type convertGeometryType(const QString &type)
Converts an ESRI REST geometry type to a WKB type.
static QgsAbstractVectorLayerLabeling * convertLabeling(const QVariantList &data)
Converts labeling JSON data to an equivalent QGIS vector labeling.
static QColor convertColor(const QVariant &data)
Converts ESRI JSON color data to a QColor object.
Compound curve geometry type.
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
Curve polygon geometry type.
virtual void setExteriorRing(QgsCurve *ring)
Sets the exterior ring of the polygon.
virtual void addInteriorRing(QgsCurve *ring)
Adds an interior ring to the geometry (takes ownership)
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
Definition: qgscurve.cpp:238
Class for parsing and evaluation of expressions (formerly called "search strings").
bool isValid() const
Checks if this expression is valid.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
void setPlacementFlags(QgsLabeling::LinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
QgsPoint endPoint() const override SIP_HOLDGIL
Returns the end point of the curve.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
Contains settings for how a map layer will be labeled.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
QuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
QString fieldName
Name of field (or an expression) to use for label text.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
A child rule for QgsRuleBasedLabeling.
void setActive(bool state)
Sets if this rule is active.
void appendChild(QgsRuleBasedLabeling::Rule *rule)
add child rule, take ownership, sets this as parent
Rule based labeling for a vector layer.
@ Cross2
Rotated cross (lines only), "x" shape.
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
Definition: qgssurface.h:43
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:38
Container for settings relating to a text buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setSize(double size)
Sets the size of the buffer.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static Type zmType(Type type, bool hasZ, bool hasM) SIP_HOLDGIL
Returns the modified input geometry type according to hasZ / hasM.
Definition: qgswkbtypes.h:831
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1246
QList< QgsRendererCategory > QgsCategoryList
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:27
const QgsCoordinateReferenceSystem & crs