LCOV - code coverage report
Current view: top level - analysis/vector/geometry_checker - qgsgeometrycheckerutils.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 207 233 88.8 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :  *  qgsgeometrycheckerutils.cpp                                            *
       3                 :            :  *  -------------------                                                    *
       4                 :            :  *  copyright            : (C) 2014 by Sandro Mani / Sourcepole AG         *
       5                 :            :  *  email                : smani@sourcepole.ch                             *
       6                 :            :  ***************************************************************************/
       7                 :            : 
       8                 :            : /***************************************************************************
       9                 :            :  *                                                                         *
      10                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      11                 :            :  *   it under the terms of the GNU General Public License as published by  *
      12                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      13                 :            :  *   (at your option) any later version.                                   *
      14                 :            :  *                                                                         *
      15                 :            :  ***************************************************************************/
      16                 :            : 
      17                 :            : #include "qgsgeometrycheckcontext.h"
      18                 :            : #include "qgsgeometrycheckerutils.h"
      19                 :            : #include "qgsgeometry.h"
      20                 :            : #include "qgsgeometryutils.h"
      21                 :            : #include "qgsfeaturepool.h"
      22                 :            : #include "qgspolygon.h"
      23                 :            : #include "qgsgeos.h"
      24                 :            : #include "qgsgeometrycollection.h"
      25                 :            : #include "qgssurface.h"
      26                 :            : #include "qgsvectorlayer.h"
      27                 :            : #include "qgsgeometrycheck.h"
      28                 :            : #include "qgsfeedback.h"
      29                 :            : 
      30                 :            : #include <qmath.h>
      31                 :            : 
      32                 :       1081 : QgsGeometryCheckerUtils::LayerFeature::LayerFeature( const QgsFeaturePool *pool,
      33                 :            :     const QgsFeature &feature,
      34                 :            :     const QgsGeometryCheckContext *context,
      35                 :            :     bool useMapCrs )
      36                 :       1081 :   : mFeaturePool( pool )
      37                 :       1081 :   , mFeature( feature )
      38                 :       1081 :   , mMapCrs( useMapCrs )
      39                 :            : {
      40                 :       1081 :   mGeometry = feature.geometry();
      41                 :       1081 :   const QgsCoordinateTransform transform( pool->crs(), context->mapCrs, context->transformContext );
      42                 :       1081 :   if ( useMapCrs && context->mapCrs.isValid() && !transform.isShortCircuited() )
      43                 :            :   {
      44                 :            :     try
      45                 :            :     {
      46                 :          0 :       mGeometry.transform( transform );
      47                 :          0 :     }
      48                 :            :     catch ( const QgsCsException & )
      49                 :            :     {
      50                 :          0 :       QgsDebugMsg( QStringLiteral( "Shrug. What shall we do with a geometry that cannot be converted?" ) );
      51                 :          0 :     }
      52                 :          0 :   }
      53                 :       1081 : }
      54                 :            : 
      55                 :        886 : QgsFeature QgsGeometryCheckerUtils::LayerFeature::feature() const
      56                 :            : {
      57                 :        886 :   return mFeature;
      58                 :            : }
      59                 :            : 
      60                 :        717 : QPointer<QgsVectorLayer> QgsGeometryCheckerUtils::LayerFeature::layer() const
      61                 :            : {
      62                 :        717 :   return mFeaturePool->layerPtr();
      63                 :            : }
      64                 :            : 
      65                 :        746 : QString QgsGeometryCheckerUtils::LayerFeature::layerId() const
      66                 :            : {
      67                 :        746 :   return mFeaturePool->layerId();
      68                 :            : }
      69                 :            : 
      70                 :       1117 : QgsGeometry QgsGeometryCheckerUtils::LayerFeature::geometry() const
      71                 :            : {
      72                 :       1117 :   return mGeometry;
      73                 :            : }
      74                 :            : 
      75                 :         29 : QString QgsGeometryCheckerUtils::LayerFeature::id() const
      76                 :            : {
      77                 :         58 :   return QStringLiteral( "%1:%2" ).arg( mFeaturePool->layerName() ).arg( mFeature.id() );
      78                 :          0 : }
      79                 :            : 
      80                 :        144 : bool QgsGeometryCheckerUtils::LayerFeature::operator==( const LayerFeature &other ) const
      81                 :            : {
      82                 :        144 :   return layerId() == other.layerId() && mFeature.id() == other.mFeature.id();
      83                 :          0 : }
      84                 :            : 
      85                 :          0 : bool QgsGeometryCheckerUtils::LayerFeature::operator!=( const LayerFeature &other ) const
      86                 :            : {
      87                 :          0 :   return layerId() != other.layerId() || mFeature.id() != other.mFeature.id();
      88                 :          0 : }
      89                 :            : 
      90                 :            : /////////////////////////////////////////////////////////////////////////////
      91                 :            : 
      92                 :        432 : QgsGeometryCheckerUtils::LayerFeatures::iterator::iterator( const QStringList::const_iterator &layerIt, const LayerFeatures *parent )
      93                 :        432 :   : mLayerIt( layerIt )
      94                 :        432 :   , mFeatureIt( QgsFeatureIds::const_iterator() )
      95                 :        432 :   , mParent( parent )
      96                 :            : {
      97                 :        432 :   nextLayerFeature( true );
      98                 :        432 : }
      99                 :            : 
     100                 :          0 : QgsGeometryCheckerUtils::LayerFeatures::iterator::iterator( const QgsGeometryCheckerUtils::LayerFeatures::iterator &rh )
     101                 :            : {
     102                 :          0 :   mLayerIt = rh.mLayerIt;
     103                 :          0 :   mFeatureIt = rh.mFeatureIt;
     104                 :          0 :   mParent = rh.mParent;
     105                 :          0 :   mCurrentFeature = std::make_unique<LayerFeature>( *rh.mCurrentFeature.get() );
     106                 :          0 : }
     107                 :            : 
     108                 :        105 : bool QgsGeometryCheckerUtils::LayerFeature::useMapCrs() const
     109                 :            : {
     110                 :        105 :   return mMapCrs;
     111                 :            : }
     112                 :        432 : QgsGeometryCheckerUtils::LayerFeatures::iterator::~iterator()
     113                 :            : {
     114                 :        432 : }
     115                 :            : 
     116                 :          0 : QgsGeometryCheckerUtils::LayerFeatures::iterator QgsGeometryCheckerUtils::LayerFeatures::iterator::operator++( int )
     117                 :            : {
     118                 :          0 :   iterator tmp( *this );
     119                 :          0 :   ++*this;
     120                 :          0 :   return tmp;
     121                 :          0 : }
     122                 :            : 
     123                 :       1073 : const QgsGeometryCheckerUtils::LayerFeature &QgsGeometryCheckerUtils::LayerFeatures::iterator::operator*() const
     124                 :            : {
     125                 :            :   Q_ASSERT( mCurrentFeature );
     126                 :       1073 :   return *mCurrentFeature;
     127                 :            : }
     128                 :            : 
     129                 :       1283 : bool QgsGeometryCheckerUtils::LayerFeatures::iterator::operator!=( const QgsGeometryCheckerUtils::LayerFeatures::iterator &other )
     130                 :            : {
     131                 :       1283 :   return mLayerIt != other.mLayerIt || mFeatureIt != other.mFeatureIt;
     132                 :            : }
     133                 :            : 
     134                 :       1067 : const QgsGeometryCheckerUtils::LayerFeatures::iterator &QgsGeometryCheckerUtils::LayerFeatures::iterator::operator++()
     135                 :            : {
     136                 :       1067 :   nextLayerFeature( false );
     137                 :       1067 :   return *this;
     138                 :            : }
     139                 :       1499 : bool QgsGeometryCheckerUtils::LayerFeatures::iterator::nextLayerFeature( bool begin )
     140                 :            : {
     141                 :       1499 :   if ( !begin && nextFeature( false ) )
     142                 :            :   {
     143                 :        841 :     return true;
     144                 :            :   }
     145                 :        732 :   while ( nextLayer( begin ) )
     146                 :            :   {
     147                 :        306 :     begin = false;
     148                 :        306 :     if ( nextFeature( true ) )
     149                 :            :     {
     150                 :        232 :       return true;
     151                 :            :     }
     152                 :            :   }
     153                 :            :   // End
     154                 :        426 :   mFeatureIt = QgsFeatureIds::const_iterator();
     155                 :        426 :   mCurrentFeature.reset();
     156                 :        426 :   return false;
     157                 :       1499 : }
     158                 :            : 
     159                 :        732 : bool QgsGeometryCheckerUtils::LayerFeatures::iterator::nextLayer( bool begin )
     160                 :            : {
     161                 :        732 :   if ( !begin )
     162                 :            :   {
     163                 :        300 :     ++mLayerIt;
     164                 :        300 :   }
     165                 :        920 :   while ( true )
     166                 :            :   {
     167                 :        920 :     if ( mLayerIt == mParent->mLayerIds.end() )
     168                 :            :     {
     169                 :        426 :       break;
     170                 :            :     }
     171                 :        494 :     if ( mParent->mGeometryTypes.contains( mParent->mFeaturePools[*mLayerIt]->geometryType() ) )
     172                 :            :     {
     173                 :        306 :       mFeatureIt = mParent->mFeatureIds[*mLayerIt].constBegin();
     174                 :        306 :       return true;
     175                 :            :     }
     176                 :        188 :     ++mLayerIt;
     177                 :            :   }
     178                 :        426 :   return false;
     179                 :        732 : }
     180                 :            : 
     181                 :       1373 : bool QgsGeometryCheckerUtils::LayerFeatures::iterator::nextFeature( bool begin )
     182                 :            : {
     183                 :       1373 :   QgsFeaturePool *featurePool = mParent->mFeaturePools[*mLayerIt];
     184                 :       1373 :   const QgsFeatureIds &featureIds = mParent->mFeatureIds[*mLayerIt];
     185                 :       1373 :   if ( !begin )
     186                 :            :   {
     187                 :       1067 :     ++mFeatureIt;
     188                 :       1067 :   }
     189                 :       1373 :   while ( true )
     190                 :            :   {
     191                 :       1373 :     if ( mFeatureIt == featureIds.end() )
     192                 :            :     {
     193                 :        300 :       break;
     194                 :            :     }
     195                 :       1073 :     if ( mParent->mFeedback )
     196                 :        490 :       mParent->mFeedback->setProgress( mParent->mFeedback->progress() + 1.0 );
     197                 :       1073 :     QgsFeature feature;
     198                 :       1073 :     if ( featurePool->getFeature( *mFeatureIt, feature ) && !feature.geometry().isNull() )
     199                 :            :     {
     200                 :       1073 :       mCurrentFeature = std::make_unique<LayerFeature>( featurePool, feature, mParent->mContext, mParent->mUseMapCrs );
     201                 :       1073 :       return true;
     202                 :            :     }
     203                 :          0 :     ++mFeatureIt;
     204                 :       1073 :   }
     205                 :        300 :   return false;
     206                 :       1373 : }
     207                 :            : 
     208                 :        189 : /////////////////////////////////////////////////////////////////////////////
     209                 :            : 
     210                 :         54 : QgsGeometryCheckerUtils::LayerFeatures::LayerFeatures( const QMap<QString, QgsFeaturePool *> &featurePools,
     211                 :            :     const QMap<QString, QgsFeatureIds> &featureIds,
     212                 :            :     const QList<QgsWkbTypes::GeometryType> &geometryTypes,
     213                 :            :     QgsFeedback *feedback,
     214                 :            :     const QgsGeometryCheckContext *context,
     215                 :            :     bool useMapCrs )
     216                 :         27 :   : mFeaturePools( featurePools )
     217                 :         27 :   , mFeatureIds( featureIds )
     218                 :         27 :   , mLayerIds( featurePools.keys() )
     219                 :         27 :   , mGeometryTypes( geometryTypes )
     220                 :         27 :   , mFeedback( feedback )
     221                 :         27 :   , mContext( context )
     222                 :         27 :   , mUseMapCrs( useMapCrs )
     223                 :         27 : {}
     224                 :            : 
     225                 :        189 : QgsGeometryCheckerUtils::LayerFeatures::LayerFeatures( const QMap<QString, QgsFeaturePool *> &featurePools,
     226                 :            :     const QList<QString> &layerIds, const QgsRectangle &extent,
     227                 :            :     const QList<QgsWkbTypes::GeometryType> &geometryTypes,
     228                 :            :     const QgsGeometryCheckContext *context )
     229                 :        189 :   : mFeaturePools( featurePools )
     230                 :        189 :   , mLayerIds( layerIds )
     231                 :        189 :   , mExtent( extent )
     232                 :        189 :   , mGeometryTypes( geometryTypes )
     233                 :        189 :   , mContext( context )
     234                 :        189 :   , mUseMapCrs( true )
     235                 :            : {
     236                 :        621 :   for ( const QString &layerId : layerIds )
     237                 :            :   {
     238                 :        432 :     const QgsFeaturePool *featurePool = featurePools[layerId];
     239                 :        432 :     if ( geometryTypes.contains( featurePool->geometryType() ) )
     240                 :            :     {
     241                 :        267 :       QgsCoordinateTransform ct( featurePool->crs(), context->mapCrs, context->transformContext );
     242                 :        267 :       mFeatureIds.insert( layerId, featurePool->getIntersects( ct.transform( extent, QgsCoordinateTransform::ReverseTransform ) ) );
     243                 :        267 :     }
     244                 :            :     else
     245                 :            :     {
     246                 :        165 :       mFeatureIds.insert( layerId, QgsFeatureIds() );
     247                 :            :     }
     248                 :            :   }
     249                 :        189 : }
     250                 :            : 
     251                 :        216 : QgsGeometryCheckerUtils::LayerFeatures::iterator QgsGeometryCheckerUtils::LayerFeatures::begin() const
     252                 :            : {
     253                 :        216 :   return iterator( mLayerIds.constBegin(), this );
     254                 :            : }
     255                 :            : 
     256                 :        216 : QgsGeometryCheckerUtils::LayerFeatures::iterator QgsGeometryCheckerUtils::LayerFeatures::end() const
     257                 :            : {
     258                 :        216 :   return iterator( mLayerIds.end(), this );
     259                 :            : }
     260                 :            : 
     261                 :            : /////////////////////////////////////////////////////////////////////////////
     262                 :            : 
     263                 :        339 : std::unique_ptr<QgsGeometryEngine> QgsGeometryCheckerUtils::createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance )
     264                 :            : {
     265                 :        339 :   return std::make_unique<QgsGeos>( geometry, tolerance );
     266                 :            : }
     267                 :            : 
     268                 :         17 : QgsAbstractGeometry *QgsGeometryCheckerUtils::getGeomPart( QgsAbstractGeometry *geom, int partIdx )
     269                 :            : {
     270                 :         17 :   if ( dynamic_cast<QgsGeometryCollection *>( geom ) )
     271                 :            :   {
     272                 :         11 :     return static_cast<QgsGeometryCollection *>( geom )->geometryN( partIdx );
     273                 :            :   }
     274                 :          6 :   return geom;
     275                 :         17 : }
     276                 :            : 
     277                 :        249 : const QgsAbstractGeometry *QgsGeometryCheckerUtils::getGeomPart( const QgsAbstractGeometry *geom, int partIdx )
     278                 :            : {
     279                 :        249 :   if ( dynamic_cast<const QgsGeometryCollection *>( geom ) )
     280                 :            :   {
     281                 :        222 :     return static_cast<const QgsGeometryCollection *>( geom )->geometryN( partIdx );
     282                 :            :   }
     283                 :         27 :   return geom;
     284                 :        249 : }
     285                 :            : 
     286                 :          9 : QList<const QgsLineString *> QgsGeometryCheckerUtils::polygonRings( const QgsPolygon *polygon )
     287                 :            : {
     288                 :          9 :   QList<const QgsLineString *> rings;
     289                 :          9 :   if ( const QgsLineString *exterior = dynamic_cast<const QgsLineString *>( polygon->exteriorRing() ) )
     290                 :            :   {
     291                 :          9 :     rings.append( exterior );
     292                 :          9 :   }
     293                 :         10 :   for ( int iInt = 0, nInt = polygon->numInteriorRings(); iInt < nInt; ++iInt )
     294                 :            :   {
     295                 :          1 :     if ( const QgsLineString *interior = dynamic_cast<const QgsLineString *>( polygon->interiorRing( iInt ) ) )
     296                 :            :     {
     297                 :          1 :       rings.append( interior );
     298                 :          1 :     }
     299                 :          1 :   }
     300                 :          9 :   return rings;
     301                 :          9 : }
     302                 :            : 
     303                 :         11 : void QgsGeometryCheckerUtils::filter1DTypes( QgsAbstractGeometry *geom )
     304                 :            : {
     305                 :         11 :   if ( qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
     306                 :            :   {
     307                 :          2 :     QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom );
     308                 :          6 :     for ( int nParts = geom->partCount(), iPart = nParts - 1; iPart >= 0; --iPart )
     309                 :            :     {
     310                 :          4 :       if ( !qgsgeometry_cast<QgsSurface *>( geomCollection->geometryN( iPart ) ) )
     311                 :            :       {
     312                 :          0 :         geomCollection->removeGeometry( iPart );
     313                 :          0 :       }
     314                 :          4 :     }
     315                 :          2 :   }
     316                 :         11 : }
     317                 :            : 
     318                 :        598 : double pointLineDist( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q )
     319                 :            : {
     320                 :        598 :   double nom = std::fabs( ( p2.y() - p1.y() ) * q.x() - ( p2.x() - p1.x() ) * q.y() + p2.x() * p1.y() - p2.y() * p1.x() );
     321                 :        598 :   double dx = p2.x() - p1.x();
     322                 :        598 :   double dy = p2.y() - p1.y();
     323                 :        598 :   return nom / std::sqrt( dx * dx + dy * dy );
     324                 :            : }
     325                 :            : 
     326                 :         44 : bool QgsGeometryCheckerUtils::pointOnLine( const QgsPoint &p, const QgsLineString *line, double tol, bool excludeExtremities )
     327                 :            : {
     328                 :         44 :   int nVerts = line->vertexCount();
     329                 :        137 :   for ( int i = 0 + excludeExtremities; i < nVerts - 1 - excludeExtremities; ++i )
     330                 :            :   {
     331                 :        109 :     QgsPoint p1 = line->vertexAt( QgsVertexId( 0, 0, i ) );
     332                 :        109 :     QgsPoint p2 = line->vertexAt( QgsVertexId( 0, 0, i + 1 ) );
     333                 :        109 :     double dist = pointLineDist( p1, p2, p );
     334                 :        109 :     if ( dist < tol )
     335                 :            :     {
     336                 :         16 :       return true;
     337                 :            :     }
     338                 :        109 :   }
     339                 :         28 :   return false;
     340                 :         44 : }
     341                 :            : 
     342                 :         19 : QList<QgsPoint> QgsGeometryCheckerUtils::lineIntersections( const QgsLineString *line1, const QgsLineString *line2, double tol )
     343                 :            : {
     344                 :         19 :   QList<QgsPoint> intersections;
     345                 :         19 :   QgsPoint inter;
     346                 :         19 :   bool intersection = false;
     347                 :         97 :   for ( int i = 0, n = line1->vertexCount() - 1; i < n; ++i )
     348                 :            :   {
     349                 :        426 :     for ( int j = 0, m = line2->vertexCount() - 1; j < m; ++j )
     350                 :            :     {
     351                 :        348 :       QgsPoint p1 = line1->vertexAt( QgsVertexId( 0, 0, i ) );
     352                 :        348 :       QgsPoint p2 = line1->vertexAt( QgsVertexId( 0, 0, i + 1 ) );
     353                 :        348 :       QgsPoint q1 = line2->vertexAt( QgsVertexId( 0, 0, j ) );
     354                 :        348 :       QgsPoint q2 = line2->vertexAt( QgsVertexId( 0, 0, j + 1 ) );
     355                 :        348 :       if ( QgsGeometryUtils::segmentIntersection( p1, p2, q1, q2, inter, intersection, tol ) )
     356                 :            :       {
     357                 :          6 :         intersections.append( inter );
     358                 :          6 :       }
     359                 :        348 :     }
     360                 :         78 :   }
     361                 :         19 :   return intersections;
     362                 :         19 : }
     363                 :            : 
     364                 :         18 : double QgsGeometryCheckerUtils::sharedEdgeLength( const QgsAbstractGeometry *geom1, const QgsAbstractGeometry *geom2, double tol )
     365                 :            : {
     366                 :         18 :   double len = 0;
     367                 :            : 
     368                 :            :   // Test every pair of segments for shared edges
     369                 :         36 :   for ( int iPart1 = 0, nParts1 = geom1->partCount(); iPart1 < nParts1; ++iPart1 )
     370                 :            :   {
     371                 :         37 :     for ( int iRing1 = 0, nRings1 = geom1->ringCount( iPart1 ); iRing1 < nRings1; ++iRing1 )
     372                 :            :     {
     373                 :        101 :       for ( int iVert1 = 0, jVert1 = 1, nVerts1 = geom1->vertexCount( iPart1, iRing1 ); jVert1 < nVerts1; iVert1 = jVert1++ )
     374                 :            :       {
     375                 :         82 :         QgsPoint p1 = geom1->vertexAt( QgsVertexId( iPart1, iRing1, iVert1 ) );
     376                 :         82 :         QgsPoint p2 = geom1->vertexAt( QgsVertexId( iPart1, iRing1, jVert1 ) );
     377                 :         82 :         double lambdap1 = 0.;
     378                 :         82 :         double lambdap2 = std::sqrt( QgsGeometryUtils::sqrDistance2D( p1, p2 ) );
     379                 :         82 :         QgsVector d;
     380                 :            :         try
     381                 :            :         {
     382                 :         82 :           d = QgsVector( p2.x() - p1.x(), p2.y() - p1.y() ).normalized();
     383                 :         82 :         }
     384                 :            :         catch ( const QgsException & )
     385                 :            :         {
     386                 :            :           // Edge has zero length, skip
     387                 :            :           continue;
     388                 :          0 :         }
     389                 :            : 
     390                 :        164 :         for ( int iPart2 = 0, nParts2 = geom2->partCount(); iPart2 < nParts2; ++iPart2 )
     391                 :            :         {
     392                 :        164 :           for ( int iRing2 = 0, nRings2 = geom2->ringCount( iPart2 ); iRing2 < nRings2; ++iRing2 )
     393                 :            :           {
     394                 :        464 :             for ( int iVert2 = 0, jVert2 = 1, nVerts2 = geom2->vertexCount( iPart2, iRing2 ); jVert2 < nVerts2; iVert2 = jVert2++ )
     395                 :            :             {
     396                 :        382 :               QgsPoint q1 = geom2->vertexAt( QgsVertexId( iPart2, iRing2, iVert2 ) );
     397                 :        382 :               QgsPoint q2 = geom2->vertexAt( QgsVertexId( iPart2, iRing2, jVert2 ) );
     398                 :            : 
     399                 :            :               // Check whether q1 and q2 are on the line p1, p
     400                 :        382 :               if ( pointLineDist( p1, p2, q1 ) <= tol && pointLineDist( p1, p2, q2 ) <= tol )
     401                 :            :               {
     402                 :            :                 // Get length common edge
     403                 :         42 :                 double lambdaq1 = QgsVector( q1.x() - p1.x(), q1.y() - p1.y() ) * d;
     404                 :         42 :                 double lambdaq2 = QgsVector( q2.x() - p1.x(), q2.y() - p1.y() ) * d;
     405                 :         42 :                 if ( lambdaq1 > lambdaq2 )
     406                 :            :                 {
     407                 :         31 :                   std::swap( lambdaq1, lambdaq2 );
     408                 :         31 :                 }
     409                 :         42 :                 double lambda1 = std::max( lambdaq1, lambdap1 );
     410                 :         42 :                 double lambda2 = std::min( lambdaq2, lambdap2 );
     411                 :         42 :                 len += std::max( 0., lambda2 - lambda1 );
     412                 :         42 :               }
     413                 :        382 :             }
     414                 :         82 :           }
     415                 :         82 :         }
     416                 :         82 :       }
     417                 :         19 :     }
     418                 :         18 :   }
     419                 :         18 :   return len;
     420                 :          0 : }

Generated by: LCOV version 1.14