From c5569abd0d045bde65f6d96938657fe28f0c2a05 Mon Sep 17 00:00:00 2001 From: gp06drap Date: Thu, 2 Jul 2026 11:29:24 -0400 Subject: [PATCH 1/5] Closes #126 Added a divide and conquer convex hull AV specifically, this one is called quickhull. Right now it is fully functional with no bugs and good UI information for the user. --- HighwayDataExaminer/hdxav-qh.js | 505 ++++++++++++++++++++++++++++++++ HighwayDataExaminer/hdxav.js | 1 + HighwayDataExaminer/index.php | 1 + 3 files changed, 507 insertions(+) create mode 100644 HighwayDataExaminer/hdxav-qh.js diff --git a/HighwayDataExaminer/hdxav-qh.js b/HighwayDataExaminer/hdxav-qh.js new file mode 100644 index 0000000..23dab03 --- /dev/null +++ b/HighwayDataExaminer/hdxav-qh.js @@ -0,0 +1,505 @@ +// +// HDX Algorithm Visualization Template File +// +// METAL Project +// +// Primary Authors: Gregory Drapeau +// + +// This global variable refers to the object containaing all the +// necessary fields, functions, and states for a given AV. This +// variable must be pushed to the this.avList in the hdxav.js file, +// and the file of this AV must be included in the index.php file +const hdxQHAV = { + // short name for list of avs, will be used for the av= QS parameter value + value: 'qhull', + name: "Quickhull", + description: "The divide and conquer algorithm for finding the convex hull.", + + // vertices, no edges + useV: true, + useE: false, + + currentSet: [], + vOne: null, + vTwo: null, + // array of vertices that makes up the hull + hullVertices: [], + // array of the line segments that makes up the hull + hullSegments: [], + // dividing line slope + slope: 0, + // dividing line y-intercept + yIntercept: 0, + // sets + sZero: [], + sOne: [], + sTwo: [], + // max [distance, index, object] + max: [], + // used for splitLoop due to that being used in multiple places + futureAction: "", + // temporary vertices for swaping + vOneTmp: null, + vTwoTmp: null, + // polyline object + compLine: null, + + // elements index 0 is v1, index 1 is v2, index 2 is lower set + recursionStack: [], + + // loop variable that tracks which point is currently being operated upon + nextToCheck: -1, + + // The avActions array defines all of the actions of the AV + avActions : [ + { + label: "START", + comment: "Initializes fields", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + const sorter = new HDXWaypointsSorter(); + thisAV.currentSet = sorter.sortWaypoints(); + thisAV.vOne = thisAV.currentSet[0]; + thisAV.vTwo = thisAV.currentSet[thisAV.currentSet.length-1]; + thisAV.currentSet.splice(0,1); + thisAV.currentSet.splice(thisAV.currentSet.length-1, 1); + // dividing line + thisAV.slope = (thisAV.vTwo.lat-thisAV.vOne.lat)/(thisAV.vTwo.lon-thisAV.vOne.lon); + thisAV.yIntercept = (thisAV.slope*(thisAV.vOne.lon*(-1)))+thisAV.vOne.lat; + let comLine = []; + comLine[0] = [thisAV.vOne.lat, thisAV.vOne.lon]; + comLine[1] = [thisAV.vTwo.lat, thisAV.vTwo.lon]; + thisAV.compLine = L.polyline(comLine, visualSettings.visiting) + thisAV.compLine.addTo(map); + thisAV.hullVertices = []; + thisAV.hullSegments = []; + thisAV.hullVertices.push(thisAV.vOne); + // AVCP updates + document.getElementById("hVertsNum").innerText = thisAV.hullVertices.length; + let newTableRow = document.createElement("tr"); + newTableRow.innerHTML = ``+thisAV.hullVertices[thisAV.hullVertices.length-1].label+` + (`+thisAV.hullVertices[thisAV.hullVertices.length-1].lat+`, `+thisAV.hullVertices[thisAV.hullVertices.length-1].lon+`)`; + document.getElementById("hullVertexTable").appendChild(newTableRow); + thisAV.nextToCheck = -1; + thisAV.futureAction = "callOne"; + hdxAVCP.update("hullv1", "v1: "+thisAV.vOne.label); + hdxAVCP.update("hullv2", "v2: "+thisAV.vTwo.label); + hdxAVCP.update("checkingLine", "Current line: y="+thisAV.slope+"x+"+thisAV.yIntercept+"
"+thisAV.vOne.label+"<-->"+thisAV.vTwo.label); + updateMarkerAndTable(waypoints.indexOf(thisAV.vOne), + visualSettings.averageCoord, 40, false); + updateMarkerAndTable(waypoints.indexOf(thisAV.vTwo), + visualSettings.averageCoord, 40, false); + + hdxAV.nextAction = "splitLoop"; + }, + logMessage: function(thisAV) { + return "Doing some setup stuff"; + } + }, + { + label: "splitLoop", + comment: "Loop for splitting up of vertices", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + thisAV.nextToCheck++; + if(thisAV.nextToCheck < thisAV.currentSet.length){ + updateMarkerAndTable(waypoints.indexOf(thisAV.currentSet[thisAV.nextToCheck]), + visualSettings.visiting, 40, false); + hdxAV.nextAction = "split"; + }else{ + hdxAV.nextAction = thisAV.futureAction; + } + }, + logMessage: function(thisAV) { + return "Loop iteration: "+thisAV.nextToCheck; + } + }, + { + label: "split", + comment: "Splitting up vertices between set one and two", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + if(thisAV.vOne.lon < thisAV.vTwo.lon){ + // above hull + if(thisAV.currentSet[thisAV.nextToCheck].lat > (thisAV.slope*thisAV.currentSet[thisAV.nextToCheck].lon+thisAV.yIntercept)){ + thisAV.sOne.push(thisAV.currentSet[thisAV.nextToCheck]); + updateMarkerAndTable(waypoints.indexOf(thisAV.currentSet[thisAV.nextToCheck]), + visualSettings.discovered, 40, false); + }else{ + thisAV.sTwo.push(thisAV.currentSet[thisAV.nextToCheck]); + updateMarkerAndTable(waypoints.indexOf(thisAV.currentSet[thisAV.nextToCheck]), + visualSettings.discarded, 40, false); + } + }else{ + // below hull + if(thisAV.currentSet[thisAV.nextToCheck].lat > (thisAV.slope*thisAV.currentSet[thisAV.nextToCheck].lon+thisAV.yIntercept)){ + thisAV.sTwo.push(thisAV.currentSet[thisAV.nextToCheck]); + updateMarkerAndTable(waypoints.indexOf(thisAV.currentSet[thisAV.nextToCheck]), + visualSettings.discarded, 40, false); + }else{ + thisAV.sOne.push(thisAV.currentSet[thisAV.nextToCheck]); + updateMarkerAndTable(waypoints.indexOf(thisAV.currentSet[thisAV.nextToCheck]), + visualSettings.discovered, 40, false); + } + } + + hdxAV.nextAction = "splitLoop"; + }, + logMessage: function(thisAV) { + return "Size of set one: "+thisAV.sOne.length+"
Size of set two: "+thisAV.sTwo.length; + } + }, + { + label: "callOne", + comment: "Adding to the recursion stack and setting up variables for first call", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + hdxAV.nextAction = "fnTop"; + + if(thisAV.sOne.length > 0){ + let snapShot + thisAV.currentSet = thisAV.sOne; + if(thisAV.sTwo.length > 0){ + snapShot = [thisAV.vTwo, thisAV.vOne, thisAV.sTwo]; + }else{ + snapShot = [thisAV.vOne]; + } + thisAV.recursionStack.push(snapShot); + }else{ + thisAV.hullVertices.push(thisAV.vTwo); + hdxQHAV.updateHullVertexTable(); + if(thisAV.sTwo.length > 0){ + let vZ = thisAV.vOne; + thisAV.vOne = thisAV.vTwo; + thisAV.vTwo = vZ; + thisAV.currentSet = thisAV.sTwo; + }else{ + hdxAV.nextAction = "cleanup"; + } + } + for(let i = 0; i < thisAV.sTwo.length ; i++){ + updateMarkerAndTable(waypoints.indexOf(thisAV.sTwo[i]), + visualSettings.discovered, 40, false); + } + + hdxAV.iterationDone = true; + }, + logMessage: function(thisAV) { + return "Starting at "+thisAV.vOne.label; + } + }, + { + label: "fnTop", + comment: "Setting up UI for new call and confirming vars have been reset", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + thisAV.nextToCheck = -1; + thisAV.max = [0,-1,null]; + thisAV.sOne = []; + thisAV.sTwo = []; + // dividing line + thisAV.slope = (thisAV.vTwo.lat-thisAV.vOne.lat)/(thisAV.vTwo.lon-thisAV.vOne.lon); + thisAV.yIntercept = (thisAV.slope*(thisAV.vOne.lon*(-1)))+thisAV.vOne.lat; + thisAV.compLine.remove(); + let comLine = []; + comLine[0] = [thisAV.vOne.lat, thisAV.vOne.lon]; + comLine[1] = [thisAV.vTwo.lat, thisAV.vTwo.lon]; + thisAV.compLine = L.polyline(comLine, visualSettings.visiting) + thisAV.compLine.addTo(map); + + // AVCP updates + hdxAVCP.update("checkingLine", "Current line: y="+thisAV.slope+"x+"+thisAV.yIntercept+"
"+thisAV.vOne.label+"<-->"+thisAV.vTwo.label); + hdxAVCP.update("hullv1", "v1: "+thisAV.vOne.label); + hdxAVCP.update("hullv2", "v2: "+thisAV.vTwo.label); + + hdxAV.nextAction = "findMaxLoop"; + }, + logMessage: function(thisAV) { + return "VOne: "+thisAV.vOne.label+" vTwo: "+thisAV.vTwo.label; + } + }, + { + label: "findMaxLoop", + comment: "Loop for finding the max", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + thisAV.nextToCheck++; + if(thisAV.nextToCheck thisAV.max[0]){ + if(thisAV.max[1] > -1){ + updateMarkerAndTable(waypoints.indexOf(thisAV.max[2]), + visualSettings.discovered, 40, false); + } + thisAV.max = [distance, thisAV.nextToCheck, thisAV.currentSet[thisAV.nextToCheck]]; + updateMarkerAndTable(waypoints.indexOf(thisAV.max[2]), + visualSettings.averageCoord, 40, false); + }else{ + updateMarkerAndTable(waypoints.indexOf(thisAV.currentSet[thisAV.nextToCheck]), + visualSettings.discovered, 40, false); + } + + hdxAV.nextAction = "findMaxLoop"; + }, + logMessage: function(thisAV) { + return "max distance is "+thisAV.max[0]; + } + }, + { + label: "firstSplit", + comment: "Prepares the variables for the first split of the set", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + if(thisAV.max[2] != null){ + thisAV.vOneTmp = thisAV.vOne; + thisAV.vTwoTmp = thisAV.max[2]; + // temporary line + thisAV.slope = (thisAV.vTwoTmp.lat-thisAV.vOneTmp.lat)/(thisAV.vTwoTmp.lon-thisAV.vOneTmp.lon); + thisAV.yIntercept = (thisAV.slope*(thisAV.vOneTmp.lon*(-1)))+thisAV.vOneTmp.lat; + thisAV.compLine.remove(); + let comLine = []; + comLine[0] = [thisAV.vOneTmp.lat, thisAV.vOneTmp.lon]; + comLine[1] = [thisAV.vTwoTmp.lat, thisAV.vTwoTmp.lon]; + thisAV.compLine = L.polyline(comLine, visualSettings.visiting) + thisAV.compLine.addTo(map); + thisAV.nextToCheck = -1; + thisAV.currentSet.splice(thisAV.max[1],1); + thisAV.futureAction = "secondSplit"; + hdxAV.nextAction = "splitLoop"; + + hdxAVCP.update("hullMax", "vmax: "+thisAV.max[2].label); + hdxAVCP.update("checkingLine", "Current line: y="+thisAV.slope+"x+"+thisAV.yIntercept+"
"+thisAV.vOneTmp.label+"<-->"+thisAV.vTwoTmp.label); + }else{ + hdxAV.nextAction = "postSplits"; + } + }, + logMessage: function(thisAV) { + return "the line is y="+thisAV.slope+"x+"+thisAV.yIntercept; + } + }, + { + label: "secondSplit", + comment: "Prepares the variables for the second split of the set", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + thisAV.vOneTmp = thisAV.max[2]; + thisAV.vTwoTmp = thisAV.vTwo; + // temporary line + thisAV.slope = (thisAV.vTwoTmp.lat-thisAV.vOneTmp.lat)/(thisAV.vTwoTmp.lon-thisAV.vOneTmp.lon); + thisAV.yIntercept = (thisAV.slope*(thisAV.vOneTmp.lon*(-1)))+thisAV.vOneTmp.lat; + thisAV.compLine.remove(); + let comLine = []; + comLine[0] = [thisAV.vOneTmp.lat, thisAV.vOneTmp.lon]; + comLine[1] = [thisAV.vTwoTmp.lat, thisAV.vTwoTmp.lon]; + thisAV.compLine = L.polyline(comLine, visualSettings.visiting) + thisAV.compLine.addTo(map); + thisAV.nextToCheck = -1; + thisAV.sZero = thisAV.sOne; + thisAV.currentSet = thisAV.sTwo; + thisAV.sOne = []; + thisAV.sTwo = []; + thisAV.futureAction = "postSplits"; + hdxAV.nextAction = "splitLoop"; + + hdxAVCP.update("checkingLine", "Current line: y="+thisAV.slope+"x+"+thisAV.yIntercept+"
"+thisAV.vOneTmp.label+"<-->"+thisAV.vTwoTmp.label); + }, + logMessage: function(thisAV) { + return "the line is y="+thisAV.slope+"x+"+thisAV.yIntercept; + } + }, + { + label: "postSplits", + comment: "Sets variables for the next iteration", + code: function(thisAV) { + highlightPseudocode(this.label, visualSettings.visiting); + + thisAV.sTwo = thisAV.sOne; + thisAV.sOne = thisAV.sZero; + hdxAV.nextAction = "fnTop"; + + // checking if set one is empty + if(thisAV.sOne.length > 0){ + let snapShot; + // checking if set two is empty + if(thisAV.sTwo.length > 0){ + snapShot = [thisAV.max[2], thisAV.vTwo, thisAV.sTwo]; + }else{ + snapShot = [thisAV.vTwo]; + } + thisAV.currentSet = thisAV.sOne; + thisAV.vTwo = thisAV.max[2]; + thisAV.recursionStack.push(snapShot); + // checking if set two is empty + }else if(thisAV.sTwo.length > 0){ + thisAV.currentSet = thisAV.sTwo; + thisAV.vOne = thisAV.max[2]; + thisAV.hullVertices.push(thisAV.max[2]); + hdxQHAV.updateHullVertexTable(); + // checking if recursion stack is empty + }else if(thisAV.recursionStack.length > 0){ + thisAV.hullVertices.push(thisAV.max[2]); + hdxQHAV.updateHullVertexTable(); + thisAV.hullVertices.push(thisAV.vTwo); + hdxQHAV.updateHullVertexTable(); + let snapShot = thisAV.recursionStack.pop(); + // collect any of the single vertices in stack + while(snapShot?.length==1){ + thisAV.hullVertices.push(snapShot[0]); + hdxQHAV.updateHullVertexTable(); + snapShot = thisAV.recursionStack.pop(); + } + // if stack is empty + if(snapShot==null){ + hdxAV.nextAction = "cleanup"; + }else{ + thisAV.vOne = snapShot[0]; + thisAV.vTwo = snapShot[1]; + thisAV.currentSet = snapShot[2]; + } + // if stack is empty + }else{ + hdxAV.nextAction = "cleanup"; + if(thisAV.max[2] != null){ + thisAV.hullVertices.push(thisAV.max[2]) + hdxQHAV.updateHullVertexTable(); + } + thisAV.hullVertices.push(thisAV.vTwo); + hdxQHAV.updateHullVertexTable(); + } + thisAV.sZero = []; + thisAV.sOne = []; + thisAV.sTwo = []; + + hdxAV.iterationDone = true; + + hdxAVCP.update("checkingLine", "Current line: y="+thisAV.slope+"x+"+thisAV.yIntercept+"
"+thisAV.vOne.label+"<-->"+thisAV.vTwo.label); + }, + logMessage: function(thisAV) { + return "Preparing for the next iteration"; + } + }, + { + label: "cleanup", + comment: "cleanup and updates at the end of the visualization", + code: function(thisAV) { + + hdxAVCP.update("hullv1", ""); + hdxAVCP.update("hullv2", ""); + hdxAVCP.update("hullMax", ""); + hdxAVCP.update("checkingLine", ""); + + thisAV.compLine.remove(); + + hdxAV.nextAction = "DONE"; + hdxAV.iterationDone = true; + + + }, + logMessage: function(thisAV) { + return "Cleanup and finalize visualization"; + } + } + ], + + // prepToStart is a required function which is called when you hit + // visualize but before you hit start + // sets up pseudocode + prepToStart() { + + // Build HTML for the pseudocode, which is an HTML table, with + // each state being a different row. + this.code = ''; + this.code += pcEntry(0,["qHull(s1, westMost, eastMost)","qHull(s2, eastMost, westMost)"],"callOne"); + this.code += pcEntry(0, ["qHull(currentSet, v1, v2)", "  if(currentSet.len==0)","     return", "  slope ← (v2.y - v1.y)/(v2.x - v1.x)", "  y-intercept ← (slope*v1.x*(-1))+y-intercept", "max ← [0, point]"], "fnTop"); + this.code += pcEntry(1, "for (i ← 0 to |currentSet-1|","findMaxLoop"); + this.code += pcEntry(2, ["if max[0] < distance(vi)", "  max ← [distance(vi)], vi"], "findMax") + this.code += pcEntry(1, ["slope1 ← (max[1].y - v1.y)/(max[1].x - v1.x)", "y-intercept1 ← (slope*v1.x*(-1))+v1.y","s1 ← split(currentSet, slope1, y-intercept1)"], "firstSplit"); + this.code += pcEntry(1, ["slope2 ← (v2.y - max[1].y)/(v2.x - max[1].x)", "y-intercept2 ← (slope*max[1].x*(-1))+max[1].y","s2 ← split(currentSet, slope2, y-intercept2)"], "secondSplit"); + this.code += pcEntry(1, ["qHull(s1, v1, max[1])", "qHull(s2, max[1]) , v2"], "postSplits"); + this.code += pcEntry(0, ["split(set, slope, y-intercept)", "  saves ← []", "  for (i ← 0 to |set-1|)"], "splitLoop"); + this.code += pcEntry(2, ["if vi.y > slope*vi.x+y-intercept", "  saves+=vi", " return saves"], "split"); + + }, + // setupUI for quickhull av + setupUI() { + + let newAO=""; + + hdxAV.algOptions.innerHTML = newAO; + + // Setting up the AVCP with the elements that will be used + hdxAVCP.add("hullv1", visualSettings.v1); + hdxAVCP.add("hullv2", visualSettings.v2); + hdxAVCP.add("hullMax", visualSettings.averageCoord); + hdxAVCP.add("checkingLine", visualSettings.visiting); + hdxAVCP.add("hullSegments", visualSettings.discovered); + const hullSeg =`Hull vertices found: 0 +
'; + this.code += 'sortedPoints[] ← sort(waypoints)
westMost ← sortedPoints[0]
eastMost ← sortedPoints[this.length-1]
slope ← (v2.y - v1.y)/(v2.x - v1.x)
y-intercept ← (slope*v1.x*(-1))+y-intercept
s1 ← split(sortedPoints, slope, y-intercept, +)
s2 ← currentSet-s1
LabelCoordinates
`; + hdxAVCP.update("hullSegments", hullSeg); + }, + // remove any changes made + cleanupUI() { + for(let i=0;i`+this.hullVertices[this.hullVertices.length-1].label+` + (`+this.hullVertices[this.hullVertices.length-1].lat+`, `+this.hullVertices[this.hullVertices.length-1].lon+`)`; + document.getElementById("hullVertexTable").appendChild(newTableRow); + } + // adding to map hull segments + let hullLine = []; + hullLine[0] = [this.hullVertices[this.hullVertices.length-2].lat, this.hullVertices[this.hullVertices.length-2].lon]; + hullLine[1] = [this.hullVertices[this.hullVertices.length-1].lat, this.hullVertices[this.hullVertices.length-1].lon]; + this.hullSegments.push(L.polyline(hullLine, visualSettings.discovered)); + this.hullSegments[this.hullSegments.length-1].addTo(map); + } +} \ No newline at end of file diff --git a/HighwayDataExaminer/hdxav.js b/HighwayDataExaminer/hdxav.js index e4a6ed7..5eb778d 100644 --- a/HighwayDataExaminer/hdxav.js +++ b/HighwayDataExaminer/hdxav.js @@ -150,6 +150,7 @@ const hdxAV = { this.avList.push(hdxVertexExtremesSearchAV); this.avList.push(hdxVertexPairsAV); this.avList.push(hdxBFConvexHullAV); + this.avList.push(hdxQHAV); this.avList.push(hdxAPClosestPointAV); this.avList.push(hdxClosestPairsRecAV); this.avList.push(hdxClickDisAV) diff --git a/HighwayDataExaminer/index.php b/HighwayDataExaminer/index.php index e910b0e..efc6426 100644 --- a/HighwayDataExaminer/index.php +++ b/HighwayDataExaminer/index.php @@ -93,6 +93,7 @@ + From 1e6cd0b3929c6d665bbe152c3ddd5953fdec4be5 Mon Sep 17 00:00:00 2001 From: gp06drap Date: Fri, 3 Jul 2026 12:56:22 -0400 Subject: [PATCH 2/5] Added suggested overlay to obscure regions not in consideration, have old dividing lines left to show the map getting divided into smaller and smaller portions recursivly and added a new recursion element to AVCP in accordance with suggestions. #126 --- HighwayDataExaminer/hdxav-qh.js | 91 ++++++++++++++++++++++++++------- 1 file changed, 73 insertions(+), 18 deletions(-) diff --git a/HighwayDataExaminer/hdxav-qh.js b/HighwayDataExaminer/hdxav-qh.js index 23dab03..532174f 100644 --- a/HighwayDataExaminer/hdxav-qh.js +++ b/HighwayDataExaminer/hdxav-qh.js @@ -43,13 +43,24 @@ const hdxQHAV = { vOneTmp: null, vTwoTmp: null, // polyline object - compLine: null, + compLine: [], // elements index 0 is v1, index 1 is v2, index 2 is lower set recursionStack: [], // loop variable that tracks which point is currently being operated upon nextToCheck: -1, + + // obscure irrelevant regions of the map + obscureRegion: null, + obscure: { + color: "black", + scale: 4, + weight: 0.5, + fillOpacity: 0.1, + name: "obscure", + value: 0 + }, // The avActions array defines all of the actions of the AV avActions : [ @@ -71,8 +82,8 @@ const hdxQHAV = { let comLine = []; comLine[0] = [thisAV.vOne.lat, thisAV.vOne.lon]; comLine[1] = [thisAV.vTwo.lat, thisAV.vTwo.lon]; - thisAV.compLine = L.polyline(comLine, visualSettings.visiting) - thisAV.compLine.addTo(map); + thisAV.compLine.push(L.polyline(comLine, visualSettings.visiting)); + thisAV.compLine[thisAV.compLine.length-1].addTo(map); thisAV.hullVertices = []; thisAV.hullSegments = []; thisAV.hullVertices.push(thisAV.vOne); @@ -181,7 +192,7 @@ const hdxQHAV = { hdxAV.nextAction = "cleanup"; } } - for(let i = 0; i < thisAV.sTwo.length ; i++){ + for(let i = 0; i < thisAV.sTwo.length; i++){ updateMarkerAndTable(waypoints.indexOf(thisAV.sTwo[i]), visualSettings.discovered, 40, false); } @@ -205,17 +216,37 @@ const hdxQHAV = { // dividing line thisAV.slope = (thisAV.vTwo.lat-thisAV.vOne.lat)/(thisAV.vTwo.lon-thisAV.vOne.lon); thisAV.yIntercept = (thisAV.slope*(thisAV.vOne.lon*(-1)))+thisAV.vOne.lat; - thisAV.compLine.remove(); let comLine = []; comLine[0] = [thisAV.vOne.lat, thisAV.vOne.lon]; comLine[1] = [thisAV.vTwo.lat, thisAV.vTwo.lon]; - thisAV.compLine = L.polyline(comLine, visualSettings.visiting) - thisAV.compLine.addTo(map); + thisAV.compLine.push(L.polyline(comLine, visualSettings.visiting)); + thisAV.compLine[thisAV.compLine.length-1].addTo(map); // AVCP updates hdxAVCP.update("checkingLine", "Current line: y="+thisAV.slope+"x+"+thisAV.yIntercept+"
"+thisAV.vOne.label+"<-->"+thisAV.vTwo.label); hdxAVCP.update("hullv1", "v1: "+thisAV.vOne.label); hdxAVCP.update("hullv2", "v2: "+thisAV.vTwo.label); + // AVCP recursionStack + let recursionTable = "Recursive Calls"; + let trSize = Math.min(4, thisAV.recursionStack.length); + for(let i = 0; i < trSize; i++){ + let recursionIndex; + if(i == 3){ + recursionIndex = 0; + recursionTable += "..."; + }else{ + recursionIndex = thisAV.recursionStack.length-i-1; + } + if(thisAV.recursionStack[recursionIndex].length == 1){ + recursionTable += ""; + }else{ + recursionTable += ""; + } + } + recursionTable += "
#"+recursionIndex+"
"+shortLabel(thisAV.recursionStack[recursionIndex][0].label, 8)+"
#"+recursionIndex+"
"+shortLabel(thisAV.recursionStack[recursionIndex][0].label, 8)+"
"+shortLabel(thisAV.recursionStack[recursionIndex][1].label, 8)+"
Size: "+thisAV.recursionStack[recursionIndex][2].length+"
"; + hdxAVCP.update("recursionStack", recursionTable); + + hdxQHAV.updateShading(); hdxAV.nextAction = "findMaxLoop"; }, @@ -281,12 +312,11 @@ const hdxQHAV = { // temporary line thisAV.slope = (thisAV.vTwoTmp.lat-thisAV.vOneTmp.lat)/(thisAV.vTwoTmp.lon-thisAV.vOneTmp.lon); thisAV.yIntercept = (thisAV.slope*(thisAV.vOneTmp.lon*(-1)))+thisAV.vOneTmp.lat; - thisAV.compLine.remove(); let comLine = []; comLine[0] = [thisAV.vOneTmp.lat, thisAV.vOneTmp.lon]; comLine[1] = [thisAV.vTwoTmp.lat, thisAV.vTwoTmp.lon]; - thisAV.compLine = L.polyline(comLine, visualSettings.visiting) - thisAV.compLine.addTo(map); + thisAV.compLine.push(L.polyline(comLine, visualSettings.visiting)); + thisAV.compLine[thisAV.compLine.length-1].addTo(map); thisAV.nextToCheck = -1; thisAV.currentSet.splice(thisAV.max[1],1); thisAV.futureAction = "secondSplit"; @@ -313,12 +343,11 @@ const hdxQHAV = { // temporary line thisAV.slope = (thisAV.vTwoTmp.lat-thisAV.vOneTmp.lat)/(thisAV.vTwoTmp.lon-thisAV.vOneTmp.lon); thisAV.yIntercept = (thisAV.slope*(thisAV.vOneTmp.lon*(-1)))+thisAV.vOneTmp.lat; - thisAV.compLine.remove(); let comLine = []; comLine[0] = [thisAV.vOneTmp.lat, thisAV.vOneTmp.lon]; comLine[1] = [thisAV.vTwoTmp.lat, thisAV.vTwoTmp.lon]; - thisAV.compLine = L.polyline(comLine, visualSettings.visiting) - thisAV.compLine.addTo(map); + thisAV.compLine.push(L.polyline(comLine, visualSettings.visiting)); + thisAV.compLine[thisAV.compLine.length-1].addTo(map); thisAV.nextToCheck = -1; thisAV.sZero = thisAV.sOne; thisAV.currentSet = thisAV.sTwo; @@ -413,8 +442,14 @@ const hdxQHAV = { hdxAVCP.update("hullv2", ""); hdxAVCP.update("hullMax", ""); hdxAVCP.update("checkingLine", ""); + hdxAVCP.update("recursionStack", ""); - thisAV.compLine.remove(); + while(thisAV.compLine.length > 0){ + thisAV.compLine[thisAV.compLine.length-1].remove(); + thisAV.compLine.pop(); + } + thisAV.obscureRegion.remove(); + thisAV.obscureRegion = null; hdxAV.nextAction = "DONE"; hdxAV.iterationDone = true; @@ -446,6 +481,10 @@ const hdxQHAV = { this.code += pcEntry(0, ["split(set, slope, y-intercept)", "  saves ← []", "  for (i ← 0 to |set-1|)"], "splitLoop"); this.code += pcEntry(2, ["if vi.y > slope*vi.x+y-intercept", "  saves+=vi", " return saves"], "split"); + const hullSeg =`Hull vertices found: 0 +
LabelCoordinates
`; + hdxAVCP.update("hullSegments", hullSeg); + }, // setupUI for quickhull av setupUI() { @@ -459,14 +498,12 @@ const hdxQHAV = { hdxAVCP.add("hullv2", visualSettings.v2); hdxAVCP.add("hullMax", visualSettings.averageCoord); hdxAVCP.add("checkingLine", visualSettings.visiting); + hdxAVCP.add("recursionStack", visualSettings.hoverV); hdxAVCP.add("hullSegments", visualSettings.discovered); - const hullSeg =`Hull vertices found: 0 -
LabelCoordinates
`; - hdxAVCP.update("hullSegments", hullSeg); }, // remove any changes made cleanupUI() { - for(let i=0;i Date: Fri, 3 Jul 2026 13:32:54 -0400 Subject: [PATCH 3/5] Fixed recursion table so that when it is collapsed, it stays collapsed. #126 --- HighwayDataExaminer/hdxav-qh.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/HighwayDataExaminer/hdxav-qh.js b/HighwayDataExaminer/hdxav-qh.js index 532174f..d8ec28f 100644 --- a/HighwayDataExaminer/hdxav-qh.js +++ b/HighwayDataExaminer/hdxav-qh.js @@ -227,7 +227,7 @@ const hdxQHAV = { hdxAVCP.update("hullv1", "v1: "+thisAV.vOne.label); hdxAVCP.update("hullv2", "v2: "+thisAV.vTwo.label); // AVCP recursionStack - let recursionTable = "Recursive Calls"; + let recursionTable = ""; let trSize = Math.min(4, thisAV.recursionStack.length); for(let i = 0; i < trSize; i++){ let recursionIndex; @@ -243,8 +243,8 @@ const hdxQHAV = { recursionTable += ""; } } - recursionTable += "
#"+recursionIndex+"
"+shortLabel(thisAV.recursionStack[recursionIndex][0].label, 8)+"
"+shortLabel(thisAV.recursionStack[recursionIndex][1].label, 8)+"
Size: "+thisAV.recursionStack[recursionIndex][2].length+"
"; - hdxAVCP.update("recursionStack", recursionTable); + recursionTable += ""; + document.getElementById("recTable").innerHTML = recursionTable; hdxQHAV.updateShading(); @@ -484,6 +484,9 @@ const hdxQHAV = { const hullSeg =`Hull vertices found: 0
LabelCoordinates
`; hdxAVCP.update("hullSegments", hullSeg); + const recursionTable = 'Recursive Calls
'; + hdxAVCP.update("recursionStack", recursionTable); + }, // setupUI for quickhull av From 49c77e9a6f6feacb644b9e11465ef61c057a6971 Mon Sep 17 00:00:00 2001 From: gp06drap Date: Sat, 4 Jul 2026 04:56:14 -0400 Subject: [PATCH 4/5] #126 refined the obscure area so that there is north and south edges making it so it doesn't just go all the way north or south depending on which side of the graph it is on. --- HighwayDataExaminer/hdxav-qh.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/HighwayDataExaminer/hdxav-qh.js b/HighwayDataExaminer/hdxav-qh.js index d8ec28f..5160449 100644 --- a/HighwayDataExaminer/hdxav-qh.js +++ b/HighwayDataExaminer/hdxav-qh.js @@ -23,6 +23,8 @@ const hdxQHAV = { currentSet: [], vOne: null, vTwo: null, + mostNorth: -90, + mostSouth: 90, // array of vertices that makes up the hull hullVertices: [], // array of the line segments that makes up the hull @@ -70,6 +72,16 @@ const hdxQHAV = { code: function(thisAV) { highlightPseudocode(this.label, visualSettings.visiting); + for(let i = 0; i < waypoints.length; i++){ + if(waypoints[i].lat > thisAV.mostNorth){ + thisAV.mostNorth = waypoints[i].lat; + } + if(waypoints[i].lat < thisAV.mostSouth){ + thisAV.mostSouth = waypoints[i].lat; + } + } + thisAV.mostNorth += (thisAV.mostNorth-thisAV.mostSouth)/10; + thisAV.mostSouth += -(thisAV.mostNorth-thisAV.mostSouth)/11; const sorter = new HDXWaypointsSorter(); thisAV.currentSet = sorter.sortWaypoints(); thisAV.vOne = thisAV.currentSet[0]; @@ -552,10 +564,10 @@ const hdxQHAV = { thisobscureRegion = [null, null]; if(this.vOne.lon < this.vTwo.lon){ // Standard - pollygonObscure = [[this.vOne.lat, this.vOne.lon], [this.vTwo.lat, this.vTwo.lon], [89, this.vTwo.lon], [89, 179], [-89, 179], [-89, -179], [89, -179], [89, this.vTwo.lon]]; + pollygonObscure = [[this.vOne.lat, this.vOne.lon], [this.vTwo.lat, this.vTwo.lon], [89, this.vTwo.lon], [89, 179], [-89, 179], [-89, -179], [89, -179], [89, this.vTwo.lon], [this.mostNorth, this.vTwo.lon], [this.mostNorth, this.vOne.lon]]; }else{ // Underside - pollygonObscure = [[this.vOne.lat, this.vOne.lon], [this.vTwo.lat, this.vTwo.lon], [-89, this.vTwo.lon], [-89, -179], [89, -179], [89, 179], [-89, 179], [-89, this.vOne.lon]]; + pollygonObscure = [[this.vOne.lat, this.vOne.lon], [this.vTwo.lat, this.vTwo.lon], [-89, this.vTwo.lon], [-89, -179], [89, -179], [89, 179], [-89, 179], [-89, this.vTwo.lon], [this.mostSouth, this.vTwo.lon], [this.mostSouth, this.vOne.lon]]; } this.obscureRegion = L.polygon(pollygonObscure, this.obscure); this.obscureRegion.addTo(map); From 764a699651913bb18bbca6dec142655dcab2aa37 Mon Sep 17 00:00:00 2001 From: gp06drap Date: Sat, 4 Jul 2026 13:01:51 -0400 Subject: [PATCH 5/5] Fixed so that it doesn't retain onld mostNorth or mostSouth numbers if you select a new graph. --- HighwayDataExaminer/hdxav-qh.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HighwayDataExaminer/hdxav-qh.js b/HighwayDataExaminer/hdxav-qh.js index 5160449..1ede449 100644 --- a/HighwayDataExaminer/hdxav-qh.js +++ b/HighwayDataExaminer/hdxav-qh.js @@ -72,6 +72,8 @@ const hdxQHAV = { code: function(thisAV) { highlightPseudocode(this.label, visualSettings.visiting); + thisAV.mostNorth = -90; + thisAV.mostSouth = 90; for(let i = 0; i < waypoints.length; i++){ if(waypoints[i].lat > thisAV.mostNorth){ thisAV.mostNorth = waypoints[i].lat;