Skip to content

v0.2.52..v0.2.53 changeset MultiaryPoi.js

Garret Voltz edited this page Feb 12, 2020 · 1 revision
diff --git a/rules/MultiaryPoi.js b/rules/MultiaryPoi.js
new file mode 100644
index 0000000..054495e
--- /dev/null
+++ b/rules/MultiaryPoi.js
@@ -0,0 +1,163 @@
+/**
+ * This script conflates groups of three or more POIs using Generic Conflation.
+ */
+
+poi = hoot.require("MultiaryPoiRules");
+
+// poor man's inheritance. Is there a better practice in JS?
+// use all the functions/parameters as defined in Poi.js, then we will override later.
+Object.keys(poi).forEach(function(key) {
+    exports[key] = poi[key];
+});
+
+exports.description = "Matches three or more POIs";
+exports.experimental = true;
+exports.geometryType = "point";
+
+// this matcher will never cause a conflict. However, another matcher may conflict with us.
+exports.neverCausesConflict = function() { return true; }
+
+delete exports.mergePair;
+
+/**
+ * Returns the match score for the three class relationships.
+ * - match
+ * - miss
+ * - review
+ *
+ * The scores should always sum to one. If they don't you will be taunted
+ * mercilessly and we'll normalize it anyway. :P
+ */
+exports.matchScore = function(map, e1, e2) {
+    totalCount += 1;
+    var result = { miss: 1.0, explain:'Miss' };
+
+    var s1 = e1.getTags().get("source");
+    var s2 = e2.getTags().get("source");
+
+//    // if either of the inputs are from wikimapia, allow intradataset conflation
+//    if ((s1 && s1.indexOf("wikimapia") !== -1) || (s2 && s2.indexOf("wikimapia") !== -1)) {
+//        // pass
+//    }
+//    // otherwise, if they aren't from wikimapia, return a miss on any intradataset comparison
+//    // e.g. OSM vs OSM always returns a miss.
+//    else if (e1.getStatusString() === e2.getStatusString()) {
+//        return result;
+//    }
+
+    var additiveResult = additiveScore(map, e1, e2);
+    var score = additiveResult.score;
+    var reasons = additiveResult.reasons;
+    var d = "(" + prettyNumber(distance(e1, e2)) + "m)";
+
+    var matchScore;
+    var classification;
+    // convert the scores into faux-probabilities
+    if (score <= 0.5) {
+        matchScore = {
+            miss: 1 - (score / 0.5 * 0.4),
+            match: score / 0.5 * 0.4,
+            explain: 'Not much evidence of a match ' + d};
+        classification = 'miss';
+    } else if (score < 1.9) {
+        matchScore = {
+            miss: 0.6 - ((score - 0.5) / 1.4 * 0.2),
+            match: 0.4 + ((score - 0.5) / 1.4 * 0.2),
+            explain: "Somewhat similar " + d + " - " + reasons.join(", ") };
+        classification = 'review';
+    } else if (score < 3) {
+        matchScore = {
+            miss: 0.4 - ((score - 1.9) / 2.1 * 0.6),
+            match: 0.6 + ((score - 1.9) / 2.1 * 0.4),
+            explain: "Somewhat similar " + d + " - " + reasons.join(", ") };
+        classification = 'match';
+    } else {
+        matchScore = {
+            miss: 0,
+            match: 1,
+            explain: "Somewhat similar " + d + " - " + reasons.join(", ") };
+        classification = 'match';
+    }
+
+    hoot.trace("***POI MATCH DETAIL***");
+    hoot.trace("e1: " + e1.getId() + ", " + e1.getTags().get("name"));
+    if (e1.getTags().get("note"))
+    {
+      hoot.trace("e1 note: " + e1.getTags().get("note"));
+    }
+    hoot.trace("e2: " + e2.getId() + ", " + e2.getTags().get("name"));
+    if (e2.getTags().get("note"))
+    {
+      hoot.trace("e2 note: " + e2.getTags().get("note"));
+    }
+    hoot.trace("score: " + score);
+    hoot.trace("explanation: " + matchScore.explain);
+    hoot.trace("classification: " + classification);
+    hoot.trace("***END POI MATCH DETAIL***");
+
+    return matchScore;
+};
+
+exports.mergeSets = function(map, pairs, replaced)
+{
+    var elements = {};
+
+    // create a dictionary that only contains each element once.
+    for (var i = 0; i < pairs.length; i++)
+    {
+        elements[pairs[i][0].toString()] = pairs[i][0];
+        elements[pairs[i][1].toString()] = pairs[i][1];
+    }
+
+    var sorted = [];
+
+    Object.keys(elements).forEach(function(key) { sorted.push(elements[key]); });
+
+    // sort all the elements by input then element ID.
+    sorted.sort(function(eid1, eid2) {
+        var e1 = map.getElement(eid1);
+        var e2 = map.getElement(eid2);
+        var s1 = e1.getStatusInput();
+        var s2 = e1.getStatusInput();
+        if (eid1 === eid2)
+        {
+            return 0;
+        }
+        if (s1 < s2 || (s1 === s2 && e1 < e2))
+        {
+            return -1;
+        }
+        else
+        {
+            return 1;
+        }
+    });
+
+    // merge so that the lower number inputs have more influence.
+    var first = null;
+    sorted.forEach(function(eid) {
+        if (first == null)
+        {
+            first = map.getElement(eid);
+        }
+        else
+        {
+            mergeElements(map, first, map.getElement(eid));
+        }
+    });
+    first.setStatusString("Conflated");
+
+    var tags = first.getTags();
+
+    if (tags.contains("hoot:hash") === false)
+    {
+        // should we automatically generate it if it doesn't exist? This would require projecting
+        // the data and stuff. ugh.
+        throw "All candidates for conflation must contain a 'hoot:hash' key value pair.";
+    }
+
+    tags.set("source:hash", tags.get("hoot:hash"));
+    tags.set("hoot:hash", "");
+    first.setTags(tags);
+};
+
Clone this wiki locally