Skip to content

v0.2.53..v0.2.54 changeset CalculateStatsOp.cpp

Garret Voltz edited this page Mar 31, 2020 · 1 revision
diff --git a/hoot-core/src/main/cpp/hoot/core/ops/CalculateStatsOp.cpp b/hoot-core/src/main/cpp/hoot/core/ops/CalculateStatsOp.cpp
index 3b214fb..05c9554 100644
--- a/hoot-core/src/main/cpp/hoot/core/ops/CalculateStatsOp.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/ops/CalculateStatsOp.cpp
@@ -52,6 +52,9 @@
 #include <hoot/core/visitors/MatchCandidateCountVisitor.h>
 #include <hoot/core/visitors/SumNumericTagsVisitor.h>
 #include <hoot/core/visitors/SchemaTranslatedTagCountVisitor.h>
+#include <hoot/core/criterion/RailwayCriterion.h>
+#include <hoot/core/criterion/PowerLineCriterion.h>
+#include <hoot/core/criterion/AreaCriterion.h>
 
 #include <math.h>
 
@@ -67,8 +70,16 @@ CalculateStatsOp::CalculateStatsOp(QString mapName, bool inputIsConflatedMapOutp
   _pConf(&conf()),
   _mapName(mapName),
   _quick(false),
-  _inputIsConflatedMapOutput(inputIsConflatedMapOutput)
+  _inputIsConflatedMapOutput(inputIsConflatedMapOutput),
+  _currentStatCalcIndex(1),
+  _totalStatCalcs(0),
+  // Unknown does not get us a usable element criterion, so skip it
+  _featureTypesToSkip(QStringList("unknown")),
+  _numInterpresetStatDataCalls(0),
+  _numInterpretStatVisCacheHits(0),
+  _numGenerateFeatureStatCalls(0)
 {
+  _initConflatableFeatureCounts();
   _readGenericStatsData();
 }
 
@@ -78,9 +89,17 @@ CalculateStatsOp::CalculateStatsOp(ElementCriterionPtr criterion, QString mapNam
   _criterion(criterion),
   _mapName(mapName),
   _quick(false),
-  _inputIsConflatedMapOutput(inputIsConflatedMapOutput)
+  _inputIsConflatedMapOutput(inputIsConflatedMapOutput),
+  _currentStatCalcIndex(1),
+  _totalStatCalcs(0),
+  _featureTypesToSkip(QStringList("unknown")),
+  _numInterpresetStatDataCalls(0),
+  _numInterpretStatVisCacheHits(0),
+  _numGenerateFeatureStatCalls(0)
 {
   LOG_VART(_inputIsConflatedMapOutput);
+
+  _initConflatableFeatureCounts();
   _readGenericStatsData();
 }
 
@@ -108,7 +127,7 @@ void CalculateStatsOp::_readGenericStatsData()
                                        {"infomin", InfoMin},
                                        {"infomax", InfoMax},
                                        {"infoaverage", InfoAverage},
-                                       {"infodiff", InfoDiff}});
+                                       {"infodiff", InfoDiff} });
 
   foreach (bpt::ptree::value_type listType, propPtree)
   {
@@ -130,10 +149,10 @@ void CalculateStatsOp::_readGenericStatsData()
         QString key = QString::fromStdString(data.first).toLower();
         QString val = QString::fromStdString(data.second.data());
 
-        if (key== "name") newStatData.name = val;
-        else if (key== "visitor") newStatData.visitor = val;
-        else if (key== "criterion") newStatData.criterion = val;
-        else if (key== "statcall")
+        if (key == "name") newStatData.name = val;
+        else if (key == "visitor") newStatData.visitor = val;
+        else if (key == "criterion") newStatData.criterion = val;
+        else if (key == "statcall")
         {
           QString enumVal = val.toLower();
           if (enumLookup.contains(enumVal))
@@ -185,13 +204,85 @@ shared_ptr<MatchCreator> CalculateStatsOp::getMatchCreator(
   return shared_ptr<MatchCreator>(); // empty if not found
 }
 
+void CalculateStatsOp::_initConflatableFeatureCounts()
+{
+  for (CreatorDescription::BaseFeatureType ft = CreatorDescription::POI;
+       ft < CreatorDescription::Unknown;
+       ft = CreatorDescription::BaseFeatureType(ft + 1))
+  {
+    _conflatableFeatureCounts[ft] = 0.0;
+  }
+}
+
+void CalculateStatsOp::_initStatCalc()
+{
+  _numInterpresetStatDataCalls = 0;
+  _numGenerateFeatureStatCalls = 0;
+
+  // Admittedly, this is a little fragile but don't have a better way to do it right now.
+
+  const int numQuickStatCalcs = _quickStatData.size();
+  LOG_VARD(numQuickStatCalcs);
+  int numSlowStatCalcs = 0;
+  _totalStatCalcs = numQuickStatCalcs;
+  if (!_quick)
+  {
+    LOG_VARD(_slowStatData.size());
+    numSlowStatCalcs = _slowStatData.size();
+    numSlowStatCalcs += 7; // the number of calls to _applyVisitor outside of a loop
+    if (ConfigOptions().getStatsTranslateScript() != "")
+    {
+      // the number of calls to _applyVisitor made during translated tag calc
+      numSlowStatCalcs += 10;
+    }
+//    LOG_VARD(_inputIsConflatedMapOutput);
+//    if (!_inputIsConflatedMapOutput)
+//    {
+//      // extra calls to _applyVisitor made for poi/poly
+//      // TODO: don't understand why these don't trigger...disabling the increment of the count of
+//      // these for now
+//      numSlowStatCalcs += 2;
+//    }
+    // for SumNumericTagsVisitor (could be rolled into _applyVisitor probably)
+    numSlowStatCalcs += 1;
+
+    LOG_VARD(MatchFactory::getInstance().getCreators().size());
+    // extra calls to _applyVisitor made by _generateFeatureStats
+    int numCallsToGenerateFeatureStats = 0;
+    for (QMap<CreatorDescription::BaseFeatureType, double>::const_iterator it =
+           _conflatableFeatureCounts.begin(); it != _conflatableFeatureCounts.end(); ++it)
+    {
+      const QString featureType = CreatorDescription::baseFeatureTypeToString(it.key());
+      if (!_featureTypesToSkip.contains(featureType, Qt::CaseInsensitive))
+      {
+        numCallsToGenerateFeatureStats++;
+      }
+    }
+    LOG_VARD(numCallsToGenerateFeatureStats);
+    numSlowStatCalcs += 5 * numCallsToGenerateFeatureStats;
+    numSlowStatCalcs += 8;  // number of length/area calcs
+
+    _totalStatCalcs += numSlowStatCalcs;
+  }
+  LOG_VARD(numSlowStatCalcs);
+  LOG_VARD(_totalStatCalcs);
+
+  _currentStatCalcIndex = 1;
+}
+
 void CalculateStatsOp::apply(const OsmMapPtr& map)
 {
-  QString logMsg = "Calculating map statistics";
+  _initStatCalc();
+
+  QString logMsg = "Performing " + QString::number(_totalStatCalcs) + " map statistic calculations";
   if (!_mapName.isEmpty())
   {
     logMsg += " for " + _mapName;
   }
+  else if (!map->getName().isEmpty())
+  {
+    logMsg += " for " + map->getName();
+  }
   logMsg += "...";
   LOG_INFO(logMsg);
 
@@ -205,31 +296,29 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
   if (!_quick)
   {
     FeatureCountVisitor featureCountVisitor;
-    _applyVisitor(&featureCountVisitor);
+    _applyVisitor(&featureCountVisitor, "Feature Count");
     const long featureCount = featureCountVisitor.getCount();
     LOG_VART(featureCount);
 
-    // _addStat("Total Features", featureCount);
-
-    vector<shared_ptr<MatchCreator>> matchCreators =
-      MatchFactory::getInstance().getCreators();
-    LOG_VARD(matchCreators.size());
     double conflatedFeatureCount =
-        _applyVisitor(new StatusCriterion(Status::Conflated), new FeatureCountVisitor());
-
-    //We're tailoring the stats to whether the map being examined is the input to a conflation job
-    //or the output from a conflation job.  When the stats option is called from the conflate
-    //command, this is accomplished by allowing the conflate command to notify this op which kind
-    //of map is being examined.  When the stats command is called by itself, there is no mechanism
-    //for doing that, so we're assuming if there are any conflated features in the map, that the map
-    //should be considered an the output of a conflation job.  This logic, of course, breaks down if
-    //the same data is conflated more than once.  In that case, it may be wise to let the stats
-    //command pass in a variable stating whether the map is input/output, like the conflate command
-    //does.
+      _applyVisitor(
+        new StatusCriterion(Status::Conflated), new FeatureCountVisitor(),
+        "Conflated Feature Count");
+
+    // We're tailoring the stats to whether the map being examined is the input to a conflation job
+    // or the output from a conflation job.  When the stats option is called from the conflate
+    // command, this is accomplished by allowing the conflate command to notify this op which kind
+    // of map is being examined.  When the stats command is called by itself, there is no mechanism
+    // for doing that, so we're assuming if there are any conflated features in the map, that the
+    // map should be considered an the output of a conflation job.  This logic, of course, breaks
+    // down if the same data is conflated more than once.  In that case, it may be wise to let the
+    // stats command pass in a variable stating whether the map is input/output, like the conflate
+    // command does.
     if (conflatedFeatureCount > 0)
     {
       _inputIsConflatedMapOutput = true;
     }
+    vector<shared_ptr<MatchCreator>> matchCreators = MatchFactory::getInstance().getCreators();
     boost::any matchCandidateCountsData;
     double conflatableFeatureCount =
       _applyVisitor(
@@ -240,10 +329,15 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
             ElementCriterionPtr(
               new NotCriterion(new NeedsReviewCriterion(_constMap)))),
           ConstElementVisitorPtr(new MatchCandidateCountVisitor(matchCreators))),
-        matchCandidateCountsData);
+        matchCandidateCountsData,
+        "Conflatable Feature Count");
     LOG_VARD(matchCreators.size());
     SumNumericTagsVisitor tagSumVis(QStringList(MetadataTags::HootPoiPolygonPoisMerged()));
+    LOG_STATUS(
+      "Calculating statistic: SumNumericTagsVisitor (" << _currentStatCalcIndex << " / " <<
+      _totalStatCalcs << ") ...");
     _constMap->visitRo(tagSumVis);
+    _currentStatCalcIndex++;
     long poisMergedIntoPolys = tagSumVis.getStat();
     //we need to add any pois that may have been merged into polygons by poi/poly into the total
     //conflated feature count
@@ -259,13 +353,15 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
     _addStat("Percentage of Total Features Conflatable",
              ((double)conflatableFeatureCount / (double)featureCount) * 100.0);
     const double numFeaturesMarkedForReview =
-      _applyVisitor(new NeedsReviewCriterion(_constMap), new FeatureCountVisitor());
+      _applyVisitor(
+        new NeedsReviewCriterion(_constMap), new FeatureCountVisitor(), "Reviewable Feature Count");
 
     CountUniqueReviewsVisitor curv;
     _constMap->visitRo(curv);
     const double numReviewsToBeMade = curv.getStat();
     const double untaggedFeatureCount =
-      _applyVisitor(new NoInformationCriterion(),new FeatureCountVisitor());
+      _applyVisitor(
+        new NoInformationCriterion(),new FeatureCountVisitor(), "No Information Feature Count");
     _addStat("Untagged Features", untaggedFeatureCount);
     long unconflatableFeatureCount = -1.0;
     if (!_inputIsConflatedMapOutput)
@@ -284,13 +380,6 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
              ((double)unconflatableFeatureCount / (double)featureCount) * 100.0);
 
     _addStat("Match Creators", matchCreators.size());
-    QMap<CreatorDescription::BaseFeatureType, double> conflatableFeatureCounts;
-    for (CreatorDescription::BaseFeatureType ft = CreatorDescription::POI;
-         ft < CreatorDescription::Unknown;
-         ft = CreatorDescription::BaseFeatureType(ft+1))
-    {
-      conflatableFeatureCounts[ft] = 0.0;
-    }
 
     const QMap<QString, long> matchCandidateCountsByMatchCreator =
       boost::any_cast<QMap<QString, long>>(matchCandidateCountsData);
@@ -323,10 +412,12 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
           //see comment in _generateFeatureStats as to why ElementCountVisitor is used here
           //instead of FeatureCountVisitor
           conflatablePolyCount =
-              _applyVisitor(new PoiPolygonPolyCriterion(), new ElementCountVisitor());
+            _applyVisitor(
+              new PoiPolygonPolyCriterion(), new ElementCountVisitor(),
+              "Conflatable Polygon Count");
         }
         _addStat("Polygons Conflatable by: " + matchCreatorName, conflatablePolyCount);
-        conflatableFeatureCounts[CreatorDescription::Polygon] += conflatablePolyCount;
+        _conflatableFeatureCounts[CreatorDescription::Polygon] += conflatablePolyCount;
 
         double conflatablePoiPolyPoiCount = 0.0;
         if (!_inputIsConflatedMapOutput)
@@ -334,13 +425,15 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
           //see comment in _generateFeatureStats as to why ElementCountVisitor is used here
           //instead of FeatureCountVisitor
           conflatablePoiPolyPoiCount =
-            _applyVisitor(new PoiPolygonPoiCriterion(), new ElementCountVisitor());
+            _applyVisitor(
+              new PoiPolygonPoiCriterion(), new ElementCountVisitor(),
+              "Conflatable POI/Polygon Count");
         }
         _addStat("POIs Conflatable by: " + matchCreatorName, conflatablePoiPolyPoiCount);
-        conflatableFeatureCounts[CreatorDescription::PoiPolygonPOI] += conflatablePoiPolyPoiCount;
+        _conflatableFeatureCounts[CreatorDescription::PoiPolygonPOI] += conflatablePoiPolyPoiCount;
       }
       _addStat("Features Conflatable by: " + matchCreatorName, conflatableFeatureCountForFeatureType);
-      conflatableFeatureCounts[featureType] += conflatableFeatureCountForFeatureType;
+      _conflatableFeatureCounts[featureType] += conflatableFeatureCountForFeatureType;
     }
 
     _addStat("Total Conflated Features", conflatedFeatureCount);
@@ -351,21 +444,18 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
              ((double)numFeaturesMarkedForReview / (double)featureCount) * 100.0);
     _addStat("Total Reviews to be Made", numReviewsToBeMade);
     const double unconflatedFeatureCount =
-      _applyVisitor(new NotCriterion(new StatusCriterion(Status::Conflated)),
-          new FeatureCountVisitor());
+      _applyVisitor(
+        new NotCriterion(new StatusCriterion(Status::Conflated)), new FeatureCountVisitor(),
+        "Unconflated Feature Count");
     _addStat("Total Unmatched Features", unconflatedFeatureCount);
     _addStat("Percentage of Total Features Unmatched",
              ((double)unconflatedFeatureCount / (double)featureCount) * 100.0);
 
-    QStringList featureTypesToSkip;
-    // Unknown does not get us a usable element criterion, so skip it
-    featureTypesToSkip.append("unknown");
     for (QMap<CreatorDescription::BaseFeatureType, double>::const_iterator it =
-           conflatableFeatureCounts.begin();
-         it != conflatableFeatureCounts.end(); ++it)
+           _conflatableFeatureCounts.begin(); it != _conflatableFeatureCounts.end(); ++it)
     {
       const QString featureType = CreatorDescription::baseFeatureTypeToString(it.key());
-      if (!featureTypesToSkip.contains(featureType, Qt::CaseInsensitive))
+      if (!_featureTypesToSkip.contains(featureType, Qt::CaseInsensitive))
       {
         _generateFeatureStats(it.key(), it.value(),
                               CreatorDescription::getFeatureCalcType(it.key()),
@@ -375,7 +465,7 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
     }
 
     LongestTagVisitor v2;
-    _applyVisitor(&v2);
+    _applyVisitor(&v2, "Longest Tag");
     _addStat("Longest Tag", v2.getStat());
 
     // TODO: this should be moved into _generateFeatureStats
@@ -387,24 +477,50 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
           ConfigOptions().getStatsTranslateScript()));
       st->setErrorTreatment(StrictOff);
       SchemaTranslatedTagCountVisitor tcv(st);
-      _applyVisitor(&tcv);
+      _applyVisitor(&tcv, "Translated Tag Count");
       _addStat("Translated Populated Tag Percent", tcv.getStat());
       _addStat("Translated Populated Tags", tcv.getPopulatedCount());
       _addStat("Translated Default Tags", tcv.getDefaultCount());
       _addStat("Translated Null Tags", tcv.getNullCount());
 
+      // TODO: make this more extensible
+      _addStat("Area Translated Populated Tag Percent",
+        _applyVisitor(
+          new AreaCriterion(map), new SchemaTranslatedTagCountVisitor(st),
+          "Translated Areas Count"));
       _addStat("Building Translated Populated Tag Percent",
-        _applyVisitor(new BuildingCriterion(map), new SchemaTranslatedTagCountVisitor(st)));
+        _applyVisitor(
+          new BuildingCriterion(map), new SchemaTranslatedTagCountVisitor(st),
+          "Translated Buildings Count"));
       _addStat("Road Translated Populated Tag Percent",
-        _applyVisitor(new HighwayCriterion(map), new SchemaTranslatedTagCountVisitor(st)));
+        _applyVisitor(
+          new HighwayCriterion(map), new SchemaTranslatedTagCountVisitor(st),
+          "Translated Roads Count"));
       _addStat("POI Translated Populated Tag Percent",
-        _applyVisitor(new PoiCriterion(), new SchemaTranslatedTagCountVisitor(st)));
-      _addStat("Waterway Translated Populated Tag Percent",
-        _applyVisitor(new LinearWaterwayCriterion(), new SchemaTranslatedTagCountVisitor(st)));
+        _applyVisitor(
+          new PoiCriterion(), new SchemaTranslatedTagCountVisitor(st), "Translated POIs Count"));
       _addStat("Polygon Conflatable POI Translated Populated Tag Percent",
-        _applyVisitor(new PoiPolygonPoiCriterion(), new SchemaTranslatedTagCountVisitor(st)));
+        _applyVisitor(
+          new PoiPolygonPoiCriterion(), new SchemaTranslatedTagCountVisitor(st),
+          "Translated POI/Polygon POIs Count"));
       _addStat("Polygon Translated Populated Tag Percent",
-        _applyVisitor(new PoiPolygonPolyCriterion(), new SchemaTranslatedTagCountVisitor(st)));
+        _applyVisitor(
+          new PoiPolygonPolyCriterion(), new SchemaTranslatedTagCountVisitor(st),
+          "Translated POI/Polygon Polygons Count"));
+      // TODO: Do we actually need to calculate a search radius here, or can we suppress that
+      // (#3380)?
+      _addStat("Power Line Translated Populated Tag Percent",
+        _applyVisitor(
+          new PowerLineCriterion(), new SchemaTranslatedTagCountVisitor(st),
+          "Translated Power Lines Count"));
+      _addStat("Railway Translated Populated Tag Percent",
+        _applyVisitor(
+          new RailwayCriterion(), new SchemaTranslatedTagCountVisitor(st),
+          "Translated Railways Count"));
+      _addStat("Waterway Translated Populated Tag Percent",
+        _applyVisitor(
+          new LinearWaterwayCriterion(), new SchemaTranslatedTagCountVisitor(st),
+          "Translated Waterways Count"));
     }
     else
     {
@@ -412,13 +528,17 @@ void CalculateStatsOp::apply(const OsmMapPtr& map)
     }
   }
 
-  logMsg = "Map statistics calculated";
+  logMsg = QString::number(_totalStatCalcs) + " map statistics calculated";
   if (!_mapName.isEmpty())
   {
     logMsg += " for " + _mapName;
   }
   logMsg += ".";
   LOG_DEBUG(logMsg);
+
+  LOG_VARD(_numInterpresetStatDataCalls);
+  LOG_VARD(_numInterpretStatVisCacheHits);
+  LOG_VARD(_numGenerateFeatureStatCalls);
 }
 
 void CalculateStatsOp::_addStat(const QString& name, double value)
@@ -448,7 +568,7 @@ void CalculateStatsOp::_interpretStatData(shared_ptr<const OsmMap>& constMap, St
         // create criterion
         pCrit =
           shared_ptr<ElementCriterion>(
-            static_cast<ElementCriterion *>(
+            static_cast<ElementCriterion*>(
               Factory::getInstance().constructObject<ElementCriterion>(d.criterion)));
 
         // make sure the map is set before we use it
@@ -493,15 +613,25 @@ void CalculateStatsOp::_interpretStatData(shared_ptr<const OsmMap>& constMap, St
 
       FilteredVisitor filteredVisitor =
         FilteredVisitor(pCrit, ConstElementVisitorPtr(pCriterionVisitor));
-      val = _applyVisitor(filteredVisitor, d.statCall);
+      val = _applyVisitor(filteredVisitor, d.name, d.statCall);
+      _numInterpresetStatDataCalls++;
     }
     else
     {
       shared_ptr<ConstElementVisitor> pVisitor;
 
       if (_appliedVisitorCache.contains(d.visitor))
-      {
+      {  
         pVisitor = _appliedVisitorCache[d.visitor];
+
+        // Even though this is cached, and its a freebie runtime-wise, we'll still log status to
+        // ensure the stat calc index remains acurate.
+        LOG_STATUS(
+          "Calculating statistic: " << d.name << " (" << _currentStatCalcIndex << "/" <<
+          _totalStatCalcs << ") ...");
+
+        _currentStatCalcIndex++;
+        _numInterpretStatVisCacheHits++;
       }
       else
       {
@@ -520,8 +650,9 @@ void CalculateStatsOp::_interpretStatData(shared_ptr<const OsmMap>& constMap, St
         }
 
         // without criterion, apply the visitor directly and interpret as NumericStatistic
-        _applyVisitor(pVisitor.get());
+        _applyVisitor(pVisitor.get(), d.name);
         _appliedVisitorCache[d.visitor] = pVisitor;
+        _numInterpresetStatDataCalls++;
       }
 
       val = GetRequestedStatValue(pVisitor.get(), d.statCall);
@@ -565,21 +696,27 @@ bool CalculateStatsOp::_matchDescriptorCompare(const CreatorDescription& m1,
 }
 
 double CalculateStatsOp::_applyVisitor(ElementCriterion* pCrit,
-                                       ConstElementVisitor* pVis, StatCall call)
+                                       ConstElementVisitor* pVis, const QString& statName,
+                                       StatCall call)
 {
-  return _applyVisitor(FilteredVisitor(pCrit, pVis), call);
+  return _applyVisitor(FilteredVisitor(pCrit, pVis), statName, call);
 }
 
-double CalculateStatsOp::_applyVisitor(const FilteredVisitor& v, StatCall call)
+double CalculateStatsOp::_applyVisitor(const FilteredVisitor& v, const QString& statName,
+                                       StatCall call)
 {
   boost::any emptyVisitorData;
-  return _applyVisitor(v, emptyVisitorData, call);
+  return _applyVisitor(v, emptyVisitorData, statName, call);
 }
 
 double CalculateStatsOp::_applyVisitor(const FilteredVisitor& v, boost::any& visitorData,
-                                       StatCall call)
+                                       const QString& statName, StatCall call)
 {
-  // this is a hack to let C++ pass v as a temporary. Bad Jason.
+  LOG_STATUS(
+    "Calculating statistic: " << statName << " (" << _currentStatCalcIndex << "/" <<
+    _totalStatCalcs << ") ...");
+
+  // this is a hack to let C++ pass v as a temporary
   FilteredVisitor* fv = const_cast<FilteredVisitor*>(&v);
   shared_ptr<FilteredVisitor> critFv;
   if (_criterion)
@@ -598,11 +735,16 @@ double CalculateStatsOp::_applyVisitor(const FilteredVisitor& v, boost::any& vis
     visitorData = dataProducer->getData();
   }
 
+  _currentStatCalcIndex++;
   return GetRequestedStatValue(&childVisitor, call);
 }
 
-void CalculateStatsOp::_applyVisitor(ConstElementVisitor *v)
+void CalculateStatsOp::_applyVisitor(ConstElementVisitor* v, const QString& statName)
 {
+  LOG_STATUS(
+    "Calculating statistic: " << statName << " (" << _currentStatCalcIndex << "/" <<
+    _totalStatCalcs << ") ...");
+
   shared_ptr<FilteredVisitor> critFv;
   if (_criterion)
   {
@@ -610,6 +752,8 @@ void CalculateStatsOp::_applyVisitor(ConstElementVisitor *v)
     v = critFv.get();
   }
   _constMap->visitRo(*v);
+
+  _currentStatCalcIndex++;
 }
 
 double CalculateStatsOp::getSingleStat(const QString& n) const
@@ -692,7 +836,8 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
   double totalFeatures = 0.0;
   totalFeatures =
     _applyVisitor(
-      FilteredVisitor(criterion->clone(), _getElementVisitorForFeatureType(featureType)));
+      FilteredVisitor(criterion->clone(), _getElementVisitorForFeatureType(featureType)),
+      "Total Feature Count: " + description);
   LOG_VARD(totalFeatures);
   _addStat(QString("%1s").arg(description), totalFeatures);
   _addStat(QString("Conflatable %1s").arg(description), conflatableCount);
@@ -702,7 +847,7 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
       FilteredVisitor(
         ChainCriterion(
           new StatusCriterion(Status::Conflated), criterion->clone()),
-      _getElementVisitorForFeatureType(featureType)));
+      _getElementVisitorForFeatureType(featureType)), "Conflated Feature Count: " + description);
   LOG_VARD(conflatedFeatureCount);
   if (featureType == CreatorDescription::PoiPolygonPOI)
   {
@@ -718,14 +863,15 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
         ChainCriterion(
           new NeedsReviewCriterion(_constMap),
           criterion->clone()),
-      _getElementVisitorForFeatureType(featureType)));
+      _getElementVisitorForFeatureType(featureType)), "Reviewable Feature Count: " + description);
   _addStat(QString("Conflated %1s").arg(description), conflatedFeatureCount);
   _addStat(QString("%1s Marked for Review").arg(description), featuresMarkedForReview);
   const double numFeatureReviewsToBeMade =
     _applyVisitor(
        FilteredVisitor(
          criterion->clone(),
-         ConstElementVisitorPtr(new CountUniqueReviewsVisitor())));
+         ConstElementVisitorPtr(new CountUniqueReviewsVisitor())),
+      "Reviews To Be Made Count: " + description);
   _addStat(QString("%1 Reviews to be Made").arg(description), numFeatureReviewsToBeMade);
 
   double unconflatedFeatureCount =
@@ -734,7 +880,7 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
         ChainCriterion(
           new NotCriterion(new StatusCriterion(Status::Conflated)),
           criterion->clone()),
-      _getElementVisitorForFeatureType(featureType)));
+      _getElementVisitorForFeatureType(featureType)), "Unconflated Feature Count: " + description);
   LOG_VARD(unconflatedFeatureCount);
   if (featureType == CreatorDescription::PoiPolygonPOI)
   {
@@ -750,7 +896,9 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
     _addStat(QString("Meters of %1 Processed by Conflation").arg(description),
       _applyVisitor(
         FilteredVisitor(ChainCriterion(ElementCriterionPtr(new StatusCriterion(Status::Conflated)),
-        criterion->clone()), ConstElementVisitorPtr(new LengthOfWaysVisitor()))));
+        criterion->clone()), ConstElementVisitorPtr(new LengthOfWaysVisitor())),
+        "Meters Processed: " + description));
+    _numGenerateFeatureStatCalls++;
   }
   else if (type == CreatorDescription::CalcTypeArea)
   {
@@ -758,7 +906,9 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
       _applyVisitor(
         FilteredVisitor(
           ChainCriterion(ElementCriterionPtr(new StatusCriterion(Status::Conflated)),
-          criterion->clone()), ConstElementVisitorPtr(new CalculateAreaVisitor()))));
+          criterion->clone()), ConstElementVisitorPtr(new CalculateAreaVisitor())),
+        "Area Processed: " + description));
+    _numGenerateFeatureStatCalls++;
   }
 
   double percentageOfTotalFeaturesConflated = 0.0;
@@ -787,6 +937,8 @@ void CalculateStatsOp::_generateFeatureStats(const CreatorDescription::BaseFeatu
   LOG_VARD(percentageOfTotalFeaturesUnconflated);
   _addStat(
     QString("Percentage of Unmatched %1s").arg(description), percentageOfTotalFeaturesUnconflated);
+
+  _numGenerateFeatureStatCalls += 5;
 }
 
 }
Clone this wiki locally