Skip to content

v0.2.52..v0.2.53 changeset ScriptMatchCreator.cpp

Garret Voltz edited this page Feb 12, 2020 · 1 revision
diff --git a/hoot-js/src/main/cpp/hoot/js/conflate/matching/ScriptMatchCreator.cpp b/hoot-js/src/main/cpp/hoot/js/conflate/matching/ScriptMatchCreator.cpp
index 1af7de2..2ba58b4 100644
--- a/hoot-js/src/main/cpp/hoot/js/conflate/matching/ScriptMatchCreator.cpp
+++ b/hoot-js/src/main/cpp/hoot/js/conflate/matching/ScriptMatchCreator.cpp
@@ -22,7 +22,7 @@
  * This will properly maintain the copyright information. DigitalGlobe
  * copyrights will be updated automatically.
  *
- * @copyright Copyright (C) 2015, 2016, 2017, 2018, 2019 DigitalGlobe (http://www.digitalglobe.com/)
+ * @copyright Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020 DigitalGlobe (http://www.digitalglobe.com/)
  */
 #include "ScriptMatchCreator.h"
 
@@ -30,7 +30,6 @@
 #include <hoot/core/conflate/matching/MatchThreshold.h>
 #include <hoot/core/conflate/matching/MatchType.h>
 #include <hoot/core/criterion/ArbitraryCriterion.h>
-#include <hoot/core/criterion/ChainCriterion.h>
 #include <hoot/core/criterion/StatusCriterion.h>
 #include <hoot/core/index/OsmMapIndex.h>
 #include <hoot/core/schema/OsmSchema.h>
@@ -39,15 +38,19 @@
 #include <hoot/core/util/Factory.h>
 #include <hoot/core/util/StringUtils.h>
 #include <hoot/core/visitors/ElementConstOsmMapVisitor.h>
-#include <hoot/core/visitors/IndexElementsVisitor.h>
-
+#include <hoot/core/visitors/SpatialIndexer.h>
+#include <hoot/core/criterion/PolygonCriterion.h>
+#include <hoot/core/criterion/NonConflatableCriterion.h>
+#include <hoot/core/criterion/PointCriterion.h>
 #include <hoot/js/conflate/matching/ScriptMatch.h>
 #include <hoot/js/elements/OsmMapJs.h>
 #include <hoot/js/elements/ElementJs.h>
+#include <hoot/core/criterion/ChainCriterion.h>
 
 // Qt
 #include <QFileInfo>
 #include <qnumeric.h>
+#include <QStringBuilder>
 #include <QElapsedTimer>
 
 // Standard
@@ -69,6 +72,8 @@ namespace hoot
 
 HOOT_FACTORY_REGISTER(MatchCreator, ScriptMatchCreator)
 
+const QString ScriptMatchCreator::POINT_POLYGON_SCRIPT_NAME = "PointPolygon.js";
+
 /**
  * Searches the specified map for any match potentials.
  */
@@ -79,7 +84,7 @@ public:
 
   ScriptMatchVisitor(const ConstOsmMapPtr& map, vector<ConstMatchPtr>& result,
     ConstMatchThresholdPtr mt, std::shared_ptr<PluginContext> script,
-                     ElementCriterionPtr filter = ElementCriterionPtr()) :
+    ElementCriterionPtr filter = ElementCriterionPtr()) :
     _map(map),
     _result(result),
     _mt(mt),
@@ -95,16 +100,36 @@ public:
     _numMatchCandidatesVisited = 0;
     _taskStatusUpdateInterval = ConfigOptions().getTaskStatusUpdateInterval();
 
+    // Calls to script functions/var are expensive, both memory-wise and processing-wise. Since this
+    // constructor gets called repeatedly by createMatch, keep them out of this constructor.
+
+    // Point/Polygon is not meant to conflate any polygons that are conflatable by other conflation
+    // routines, hence the use of NonConflatableCriterion.
+    _pointPolyCrit.reset(
+      new ChainCriterion(
+        ElementCriterionPtr(new PolygonCriterion()),
+        ElementCriterionPtr(new NonConflatableCriterion(map))));
+  }
+
+  ~ScriptMatchVisitor()
+  {
+  }
+
+  void initSearchRadiusInfo()
+  {
+    LOG_DEBUG("Initializing search radius info...");
+
     Isolate* current = v8::Isolate::GetCurrent();
     HandleScope handleScope(current);
     Context::Scope context_scope(_script->getContext(current));
     Handle<Object> plugin = getPlugin();
+
     _candidateDistanceSigma = getNumber(plugin, "candidateDistanceSigma", 0.0, 1.0);
 
     //this is meant to have been set externally in a js rules file
     _customSearchRadius =
       getNumber(plugin, "searchRadius", -1.0, ConfigOptions().getCircularErrorDefaultValue());
-    LOG_VART(_customSearchRadius);
+    LOG_VARD(_customSearchRadius);
 
     Handle<Value> value = plugin->Get(toV8("getSearchRadius"));
     if (value->IsUndefined())
@@ -121,10 +146,6 @@ public:
     }
   }
 
-  ~ScriptMatchVisitor()
-  {
-  }
-
   virtual QString getDescription() const { return ""; }
 
   void checkForMatch(const std::shared_ptr<const Element>& e)
@@ -135,29 +156,58 @@ public:
     Persistent<Object> plugin(current, getPlugin(_script));
     Local<Object> mapJs(ToLocal(&_mapJs));
 
+    LOG_VART(e->getElementId());
+
     ConstOsmMapPtr map = getMap();
 
-    // create an envlope around the e plus the search radius.
+    // create an envelope around the e plus the search radius.
     std::shared_ptr<Envelope> env(e->getEnvelope(map));
-    LOG_VART(env);
     Meters searchRadius = getSearchRadius(e);
+    LOG_VART(searchRadius);
     env->expandBy(searchRadius);
+    LOG_VART(env);
 
     // find other nearby candidates
-    set<ElementId> neighbors =
-      IndexElementsVisitor::findNeighbors(*env, getIndex(), _indexToEid, getMap());
+    LOG_TRACE(
+      "Finding neighbors for: " << e->getElementId() << " during conflation: " << _scriptPath <<
+      "...");
+    const set<ElementId> neighbors =
+      SpatialIndexer::findNeighbors(*env, getIndex(), _indexToEid, map);
+    LOG_VART(neighbors);
     ElementId from = e->getElementId();
 
     _elementsEvaluated++;
 
+    const bool isPointPolyConflation =
+      _scriptPath.contains(ScriptMatchCreator::POINT_POLYGON_SCRIPT_NAME);
     for (set<ElementId>::const_iterator it = neighbors.begin(); it != neighbors.end(); ++it)
     {
       ConstElementPtr e2 = map->getElement(*it);
-      LOG_VART(e2.get());
-      if (isCorrectOrder(e, e2) && isMatchCandidate(e2))
+      LOG_VART(e2->getElementId());
+
+      // isCorrectOrder and isMatchCandidate don't apply to Point/Polygon, so we add a different
+      // workflow for it here. All other generic scripts use isMatchCandidate to identify both
+      // what to index and what to match and that doesn't work for Point/Polygon matching two
+      // different geometries. See related note in getIndex about Point/Polygon.
+
+      bool attemptToMatch = false;
+      if (!isPointPolyConflation)
+      {
+        LOG_VART(isCorrectOrder(e, e2));
+        LOG_VART(isMatchCandidate(e2));
+        attemptToMatch = isCorrectOrder(e, e2) && isMatchCandidate(e2);
+      }
+      else
+      {
+        attemptToMatch = _pointPolyCrit->isSatisfied(e2);
+      }
+      LOG_VART(attemptToMatch);
+
+      if (attemptToMatch)
       {
         // score each candidate and push it on the result vector
-        std::shared_ptr<ScriptMatch> m(new ScriptMatch(_script, plugin, map, mapJs, from, *it, _mt));
+        std::shared_ptr<ScriptMatch> m(
+          new ScriptMatch(_script, plugin, map, mapJs, from, *it, _mt));
         // if we're confident this is a miss
         if (m->getType() != MatchType::Miss)
         {
@@ -233,11 +283,13 @@ public:
       if (_customSearchRadius < 0)
       {
         //base the radius off of the element itself
+        LOG_TRACE("Calculating search radius based off of element...");
         result = e->getCircularError() * _candidateDistanceSigma;
       }
       else
       {
         //base the radius off some predefined radius
+        LOG_TRACE("Calculating search radius based off of custom defined script value...");
         result = _customSearchRadius * _candidateDistanceSigma;
       }
     }
@@ -245,10 +297,13 @@ public:
     {
       if (_searchRadiusCache.contains(e->getElementId()))
       {
+        LOG_TRACE("Retrieving search radius from cache...");
         result = _searchRadiusCache[e->getElementId()];
       }
       else
       {
+        LOG_TRACE("Calling getSearchRadius function for: " << _scriptPath << "...");
+
         Isolate* current = v8::Isolate::GetCurrent();
         HandleScope handleScope(current);
         Context::Scope context_scope(_script->getContext(current));
@@ -258,7 +313,6 @@ public:
         int argc = 0;
         jsArgs[argc++] = ElementJs::New(e);
 
-        LOG_TRACE("Calling getSearchRadius...");
         Handle<Value> f = ToLocal(&_getSearchRadius)->Call(getPlugin(), argc, jsArgs);
 
         result = toCpp<Meters>(f) * _candidateDistanceSigma;
@@ -267,14 +321,15 @@ public:
       }
     }
 
+    LOG_VART(result);
     return result;
   }
 
   void calculateSearchRadius()
   {
-    /*
-     * This is meant to run one time when the match creator is initialized.
-     */
+    // This is meant to run one time when the match creator is initialized.
+
+    LOG_DEBUG("Checking for existence of search radius export for: " << _scriptPath << "...");
 
     Isolate* current = v8::Isolate::GetCurrent();
     HandleScope handleScope(current);
@@ -295,7 +350,7 @@ public:
       return;
     }
 
-    LOG_DEBUG("Calculating search radius for: " << _scriptPath << "...");
+    LOG_STATUS("Calculating search radius for: " << _scriptPath << "...");
 
     Handle<Function> func = Handle<Function>::Cast(value);
     Handle<Value> jsArgs[1];
@@ -327,33 +382,69 @@ public:
   {
     if (!_index)
     {
+      LOG_INFO("Creating script feature index for: " << _scriptPath << "...");
+
       // No tuning was done, I just copied these settings from OsmMapIndex.
       // 10 children - 368 - see #3054
       std::shared_ptr<MemoryPageStore> mps(new MemoryPageStore(728));
       _index.reset(new HilbertRTree(mps, 2));
 
-      // Only index elements that satisfy the isMatchCandidate
-      // previously we only indexed Unknown2, but that causes issues when wanting to conflate
-      // from n datasets and support intradataset conflation. This approach over-indexes a bit and
-      // will likely slow things down, but should give the same results.
-      // An option in the future would be to support an "isIndexedFeature" or similar function
-      // to speed the operation back up again.
-      std::function<bool (ConstElementPtr)> f =
-        std::bind(&ScriptMatchVisitor::isMatchCandidate, this, placeholders::_1);
-      std::shared_ptr<ArbitraryCriterion> pC(new ArbitraryCriterion(f));
+      // Only index elements that satisfy the isMatchCandidate. Previously we only indexed Unknown2,
+      // but that causes issues when wanting to conflate from n datasets and support intra-dataset
+      // conflation. This approach over-indexes a bit and will likely slow things down, but should
+      // give the same results. An option in the future would be to support an "isIndexedFeature" or
+      // similar function to speed the operation back up again.
 
-      // Instantiate our visitor
-      IndexElementsVisitor v(_index,
-                             _indexToEid,
-                             pC,
-                             std::bind(&ScriptMatchVisitor::getSearchRadius, this, placeholders::_1),
-                             getMap());
+      // Point/Polygon conflation behaves diferently than all other generic scripts in that it
+      // conflates geometries of different types. This class wasn't really originally designed to
+      // handle that, so we add a logic path here to accommodate Point/Polygon.
+      if (!_scriptPath.contains(ScriptMatchCreator::POINT_POLYGON_SCRIPT_NAME))
+      {
+        std::function<bool (ConstElementPtr)> f =
+          std::bind(&ScriptMatchVisitor::isMatchCandidate, this, placeholders::_1);
+        std::shared_ptr<ArbitraryCriterion> pC(new ArbitraryCriterion(f));
+
+        SpatialIndexer v(_index,
+                         _indexToEid,
+                         pC,
+                         std::bind(&ScriptMatchVisitor::getSearchRadius, this, placeholders::_1),
+                         getMap());
+        switch (_scriptInfo.geometryType)
+        {
+          case GeometryTypeCriterion::GeometryType::Point:
+            getMap()->visitNodesRo(v);
+            break;
+          case GeometryTypeCriterion::GeometryType::Line:
+            getMap()->visitWaysRo(v);
+            getMap()->visitRelationsRo(v);
+            break;
+          case GeometryTypeCriterion::GeometryType::Polygon:
+            getMap()->visitWaysRo(v);
+            getMap()->visitRelationsRo(v);
+            break;
+          default:
+            // visit all geometry types if the script didn't identify its geometry
+            getMap()->visitRo(v);
+            break;
+        }
+        v.finalizeIndex();
+      }
+      else
+      {
+        // Point/Polygon identifies points as match candidates, so we just index all polygons here.
+        SpatialIndexer v(_index,
+                         _indexToEid,
+                         _pointPolyCrit,
+                         std::bind(&ScriptMatchVisitor::getSearchRadius, this, placeholders::_1),
+                         getMap());
+        getMap()->visitWaysRo(v);
+        getMap()->visitRelationsRo(v);
+        v.finalizeIndex();
+      }
+      LOG_VART(_indexToEid.size());
 
-      // Do the visiting
-      getMap()->visitRo(v);
-      v.finalizeIndex();
+      LOG_DEBUG("Script feature index created for: " << _scriptPath << ".");
     }
-
     return _index;
   }
 
@@ -377,6 +468,11 @@ public:
    */
   bool isCorrectOrder(const ConstElementPtr& e1, const ConstElementPtr& e2)
   {
+    LOG_VART(e1->getStatus().getEnum());
+    LOG_VART(e2->getStatus().getEnum());
+    LOG_VART(e1->getElementId());
+    LOG_VART(e2->getElementId());
+
     if (e1->getStatus().getEnum() == e2->getStatus().getEnum())
     {
       return e1->getElementId() < e2->getElementId();
@@ -459,10 +555,10 @@ public:
       Handle<Value> f = func->Call(ToLocal(&plugin), argc, jsArgs);
 
       result = f->BooleanValue();
-      LOG_VART(result);
     //}
 
     _matchCandidateCache[e->getElementId()] = result;
+
     return result;
   }
 
@@ -496,8 +592,16 @@ public:
   Meters getCustomSearchRadius() const { return _customSearchRadius; }
   void setCustomSearchRadius(Meters searchRadius) { _customSearchRadius = searchRadius; }
 
+  double getCandidateDistanceSigma() const { return _candidateDistanceSigma; }
+  void setCandidateDistanceSigma(double sigma) { _candidateDistanceSigma = sigma; }
+
+  CreatorDescription getCreatorDescription() const { return _scriptInfo; }
+  void setCreatorDescription(const CreatorDescription& description) { _scriptInfo = description; }
+
   long getNumMatchCandidatesFound() const { return _numMatchCandidatesVisited; }
 
+  bool hasCustomSearchRadiusFunction() const { return !_getSearchRadius.IsEmpty(); }
+
 private:
 
   // don't hold on to the map.
@@ -512,12 +616,13 @@ private:
   size_t _maxGroupSize;
   ConstMatchThresholdPtr _mt;
   std::shared_ptr<PluginContext> _script;
+  CreatorDescription _scriptInfo;
   ElementCriterionPtr _filter;
   Persistent<Function> _getSearchRadius;
 
   QHash<ElementId, bool> _matchCandidateCache;
   QHash<ElementId, double> _searchRadiusCache;
-  QMap<QString, ElementCriterionPtr> _matchCandidateCriterionCache;
+  //QMap<QString, ElementCriterionPtr> _matchCandidateCriterionCache;
 
   // Used for finding neighbors
   std::shared_ptr<HilbertRTree> _index;
@@ -532,6 +637,8 @@ private:
   long _numElementsVisited;
   long _numMatchCandidatesVisited;
   int _taskStatusUpdateInterval;
+
+  std::shared_ptr<ChainCriterion> _pointPolyCrit;
 };
 
 ScriptMatchCreator::ScriptMatchCreator()
@@ -543,6 +650,11 @@ ScriptMatchCreator::~ScriptMatchCreator()
 {
 }
 
+void ScriptMatchCreator::init(const ConstOsmMapPtr& map)
+{
+  _getCachedVisitor(map)->initSearchRadiusInfo();
+}
+
 Meters ScriptMatchCreator::calculateSearchRadius(const ConstOsmMapPtr& map,
   const ConstElementPtr& e)
 {
@@ -566,13 +678,57 @@ void ScriptMatchCreator::setArguments(QStringList args)
   _description = QString::fromStdString(className()) + "," + args[0];
   _cachedScriptVisitor.reset();
 
-  QFileInfo scriptFileInfo(_scriptPath);
-  LOG_DEBUG("Set arguments for: " << className() << " - rules: " << scriptFileInfo.fileName());
+  LOG_DEBUG(
+    "Set arguments for: " << className() << " - rules: " << QFileInfo(_scriptPath).fileName());
 }
 
-MatchPtr ScriptMatchCreator::createMatch(const ConstOsmMapPtr& map, ElementId eid1, ElementId eid2)
+MatchPtr ScriptMatchCreator::createMatch(const ConstOsmMapPtr& map, ElementId eid1,
+                                         ElementId eid2)
 {
-  if (isMatchCandidate(map->getElement(eid1), map) && isMatchCandidate(map->getElement(eid2), map))
+  LOG_VART(eid1);
+  LOG_VART(eid2);
+
+  // There may be some benefit at some point in caching matches calculated in ScriptMatchVisitor and
+  // accessing that cached information here to avoid extra calls into the JS match script. So far,
+  // haven't seen any performance improvement after adding match caching.
+
+  const bool isPointPolyConflation = _scriptPath.contains(POINT_POLYGON_SCRIPT_NAME);
+  LOG_VART(isPointPolyConflation);
+  bool attemptToMatch = false;
+  ConstElementPtr e1 = map->getElement(eid1);
+  ConstElementPtr e2 = map->getElement(eid2);
+  if (e1 && e2)
+  {
+    if (!isPointPolyConflation)
+    {
+      attemptToMatch = isMatchCandidate(e1, map) && isMatchCandidate(e2, map);
+    }
+    else
+    {
+      if (!_pointPolyPolyCrit)
+      {
+        _pointPolyPolyCrit.reset(
+          new ChainCriterion(
+            ElementCriterionPtr(new PolygonCriterion()),
+            ElementCriterionPtr(new NonConflatableCriterion(map))));
+      }
+      if (!_pointPolyPointCrit)
+      {
+        _pointPolyPointCrit.reset(
+          new ChainCriterion(
+            ElementCriterionPtr(new PointCriterion(map)),
+            ElementCriterionPtr(new NonConflatableCriterion(map))));
+      }
+
+      // see related note in ScriptMatchVisitor::checkForMatch
+      attemptToMatch =
+        (_pointPolyPointCrit->isSatisfied(e1) && _pointPolyPolyCrit->isSatisfied(e2)) ||
+        (_pointPolyPolyCrit->isSatisfied(e1) && _pointPolyPointCrit->isSatisfied(e2));
+    }
+  }
+  LOG_VART(attemptToMatch);
+
+  if (attemptToMatch)
   {
     Isolate* current = v8::Isolate::GetCurrent();
     HandleScope handleScope(current);
@@ -583,38 +739,89 @@ MatchPtr ScriptMatchCreator::createMatch(const ConstOsmMapPtr& map, ElementId ei
 
     return MatchPtr(new ScriptMatch(_script, plugin, map, mapJs, eid1, eid2, getMatchThreshold()));
   }
+
   return MatchPtr();
 }
 
-void ScriptMatchCreator::createMatches(const ConstOsmMapPtr& map, std::vector<ConstMatchPtr>& matches,
-                                       ConstMatchThresholdPtr threshold)
+void ScriptMatchCreator::createMatches(
+  const ConstOsmMapPtr& map, std::vector<ConstMatchPtr>& matches, ConstMatchThresholdPtr threshold)
 {
   if (!_script)
   {
     throw IllegalArgumentException("The script must be set on the ScriptMatchCreator.");
   }
 
-  const CreatorDescription scriptInfo = _getScriptDescription(_scriptPath);
+  QElapsedTimer timer;
+  timer.start();
 
   ScriptMatchVisitor v(map, matches, threshold, _script, _filter);
   v.setScriptPath(_scriptPath);
+  const CreatorDescription scriptInfo = _getScriptDescription(_scriptPath);
+  _descriptionCache[_scriptPath] = scriptInfo;
+  v.setCreatorDescription(scriptInfo);
+  v.initSearchRadiusInfo();
   v.calculateSearchRadius();
 
-  _cachedCustomSearchRadii[_scriptPath] = v.getCustomSearchRadius();
-  LOG_VART(_scriptPath);
-  LOG_VART(_cachedCustomSearchRadii[_scriptPath]);
   QFileInfo scriptFileInfo(_scriptPath);
-
-  QElapsedTimer timer;
-  timer.start();
-  LOG_INFO(
-    "Looking for matches with: " << className() << ";" << scriptFileInfo.fileName() << "...");
+  // This doesn't work with _candidateDistanceSigma, but right now its set to 1.0 in every script
+  // and has no effect on the search radius.
+  QString searchRadiusStr;
+  const double searchRadius = v.getCustomSearchRadius();
+  if (v.hasCustomSearchRadiusFunction())
+  {
+    searchRadiusStr = "within a function calculated search radius";
+  }
+  else if (searchRadius < 0)
+  {
+    searchRadiusStr = "within a feature dependent search radius";
+  }
+  else
+  {
+    searchRadiusStr =
+      "within a search radius of " + QString::number(searchRadius, 'g', 2) + " meters";
+  }
+  LOG_STATUS(
+    "Looking for matches with: " << className() << ";" << scriptFileInfo.fileName() << " " <<
+     searchRadiusStr << "...");
   LOG_VARD(*threshold);
-  map->visitRo(v);
-  LOG_INFO(
+  const int matchesSizeBefore = matches.size();
+
+  _cachedCustomSearchRadii[_scriptPath] = searchRadius;
+  _candidateDistanceSigmaCache[_scriptPath] = v.getCandidateDistanceSigma();
+
+  LOG_VARD(GeometryTypeCriterion::typeToString(scriptInfo.geometryType));
+  switch (scriptInfo.geometryType)
+  {
+    case GeometryTypeCriterion::GeometryType::Point:
+      map->visitNodesRo(v);
+      break;
+    case GeometryTypeCriterion::GeometryType::Line:
+      map->visitWaysRo(v);
+      map->visitRelationsRo(v);
+      break;
+    case GeometryTypeCriterion::GeometryType::Polygon:
+      map->visitWaysRo(v);
+      map->visitRelationsRo(v);
+      break;
+    default:
+      // visit all geometry types if the script didn't identify its geometry
+      map->visitRo(v);
+      break;
+  }
+  const int matchesSizeAfter = matches.size();
+
+  QString matchType = CreatorDescription::baseFeatureTypeToString(scriptInfo.baseFeatureType);
+  // Workaround for the Point/Polygon script since it doesn't identify a base feature type. See
+  // note in ScriptMatchVisitor::getIndex and rules/PointPolygon.js.
+  if (_scriptPath.contains(POINT_POLYGON_SCRIPT_NAME))
+  {
+    matchType = "PointPolygon";
+  }
+  LOG_STATUS(
     "Found " << StringUtils::formatLargeNumber(v.getNumMatchCandidatesFound()) << " " <<
-    CreatorDescription::baseFeatureTypeToString(scriptInfo.baseFeatureType) <<
-    " match candidates in: " << StringUtils::millisecondsToDhms(timer.elapsed()) << ".");
+    matchType << " match candidates and " <<
+    StringUtils::formatLargeNumber(matchesSizeAfter - matchesSizeBefore) <<
+    " total matches in: " << StringUtils::millisecondsToDhms(timer.elapsed()) << ".");
 }
 
 vector<CreatorDescription> ScriptMatchCreator::getAllCreators() const
@@ -665,12 +872,28 @@ std::shared_ptr<ScriptMatchVisitor> ScriptMatchCreator::_getCachedVisitor(
     LOG_VART(scriptPath);
 
     QFileInfo scriptFileInfo(_scriptPath);
-    LOG_TRACE("Resetting the match candidate checker " << scriptFileInfo.fileName() << "...");
+    LOG_TRACE("Resetting the match candidate checker: " << scriptFileInfo.fileName() << "...");
 
     vector<ConstMatchPtr> emptyMatches;
     _cachedScriptVisitor.reset(
       new ScriptMatchVisitor(map, emptyMatches, ConstMatchThresholdPtr(), _script, _filter));
+
     _cachedScriptVisitor->setScriptPath(scriptPath);
+
+    // setting these cached values on the visitor here for performance reasons; this could all be
+    // consolidated and cleaned up
+    LOG_VART(_descriptionCache.contains(scriptPath));
+    if (_descriptionCache.contains(scriptPath))
+    {
+      _cachedScriptVisitor->setCreatorDescription(_descriptionCache[scriptPath]);
+    }
+
+    LOG_VART(_candidateDistanceSigmaCache.contains(scriptPath));
+    if (_candidateDistanceSigmaCache.contains(scriptPath))
+    {
+      _cachedScriptVisitor->setCandidateDistanceSigma(_candidateDistanceSigmaCache[scriptPath]);
+    }
+
     //If the search radius has already been calculated for this matcher once, we don't want to do
     //it again due to the expense.
     LOG_VART(_cachedCustomSearchRadii.contains(scriptPath));
@@ -690,6 +913,8 @@ std::shared_ptr<ScriptMatchVisitor> ScriptMatchCreator::_getCachedVisitor(
 
 CreatorDescription ScriptMatchCreator::_getScriptDescription(QString path) const
 {
+  LOG_DEBUG("Getting script description...");
+
   CreatorDescription result;
   result.experimental = true;
 
@@ -718,6 +943,12 @@ CreatorDescription ScriptMatchCreator::_getScriptDescription(QString path) const
     Handle<Value> value = ToLocal(&plugin)->Get(featureTypeStr);
     result.baseFeatureType = CreatorDescription::stringToBaseFeatureType(toCpp<QString>(value));
   }
+  Handle<String> geometryTypeStr = String::NewFromUtf8(current, "geometryType");
+  if (ToLocal(&plugin)->Has(geometryTypeStr))
+  {
+    Handle<Value> value = ToLocal(&plugin)->Get(geometryTypeStr);
+    result.geometryType = GeometryTypeCriterion::typeFromString(toCpp<QString>(value));
+  }
 
   QFileInfo fi(path);
   result.className = (QString::fromStdString(className()) + "," + fi.fileName()).toStdString();
@@ -774,4 +1005,10 @@ std::shared_ptr<MatchThreshold> ScriptMatchCreator::getMatchThreshold()
   return _matchThreshold;
 }
 
+QString ScriptMatchCreator::getName() const
+{
+  QFileInfo scriptFileInfo(_scriptPath);
+  return QString::fromStdString(className()) + ";" + scriptFileInfo.fileName();
+}
+
 }
Clone this wiki locally