Reversewigo solver ✓🐵

Reversewigo solver ✓🐵 - solve reverse wherigo automatically on cache page. Solver code originated from Rick Rickhardson's reverse-wherigo source (public domain). Also, creates show source button for mystery cacahes.

Ekde 2021/04/29. Vidu La ĝisdata versio.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Reversewigo solver ✓🐵
// @namespace    http://tampermonkey.net/
// @version      0.09 DEV
// @description  Reversewigo solver ✓🐵  - solve reverse wherigo automatically on cache page. Solver code originated from Rick Rickhardson's reverse-wherigo source (public domain). Also, creates show source button for mystery cacahes.
// @author       [email protected]
// @include      https://www.geocaching.com/geocache/*
// ==/UserScript==

// placeholder for calculated reverse wherigo coordinates
// used by pollDOM to update them to popup dialog
// TODO: definitely not the best practise but a quick hack that seems to work (for now)
var newCoordinates = "";


/* async */ function dd2dm(latdd, londd) {
    var lat = latdd;
    var lon = londd;

    var latResult, lonResult, dmsResult;

    lat = parseFloat(lat);
    lon = parseFloat(lon);

    latResult = /* await */ lat_dd2dm(lat);
    lonResult = /* await */ lon_dd2dm(lon);

    // Joining both variables and separate them with a space.
    dmsResult = latResult + ' ' + lonResult;

    // Return the resultant string
    return { result: dmsResult, lat: latResult, lon: lonResult };
}

/* async */ function lat_dd2dm(val) {
    var valDeg, valMin, result;

    val = Number(val);
    result = (val >= 0)? 'N' : 'S';
    result += " ";

    val = Math.abs(val);
    valDeg = Math.floor(val);

    if( valDeg < 10 ) {
    result += "0" + valDeg;
    }
    else {
    result += valDeg;
    }
    result += "\xB0";
    result += " ";

    valMin = ((val - valDeg) * 60).toFixed(3);
    if( valMin < 10) {
        result += "0" + valMin;
    }
    else {
        result += valMin;
    }
    return result;
}

/* async */ function lon_dd2dm(val) {
    var valDeg, valMin, result;

    val = Number(val);

    result = (val >= 0)? 'E' : 'W';
    result += " ";

    val = Math.abs(val);
    valDeg = Math.floor(val);

    if( valDeg < 10 ) {
        result += "00" + valDeg;
    }
    else if( valDeg < 100) {
        result += "0" + valDeg;
    }
    else {
        result += valDeg;
    }
    result += "\xB0";
    result += " ";

    valMin = ((val - valDeg) * 60).toFixed(3);
    if( valMin < 10) {
        result += "0" + valMin;
    }
    else {
        result += valMin;
    }
    return result;
}

/* async */ function code2LatLon(varA, varB, varC) {
    let latSign, lonSign, lonValue, latValue;

    // 123456 => digit 1 (d1) = 6; digit 2 (d2) = 5; ...
    // syntax for varA => digit 1 var A = A1; digit 2 varA = A2; ...
    // A1
    if ((varA % 1000 - varA % 100) / 100 == 1) {
        latSign = 1;
        lonSign = 1;
    }
    // A1
    else if ((varA % 1000 - varA % 100) / 100 == 2) {
        latSign = -1;
        lonSign = 1;
    }
    // A1
    else if ((varA % 1000 - varA % 100) / 100 == 3) {
        latSign = 1;
        lonSign = -1;
    }
    // A1
    else if ((varA % 1000 - varA % 100) / 100 == 4) {
        latSign = -1;
        lonSign = -1;
    }
//T41140 / Q1TQ01 / 14S4RS
    // A6 B3 B4 B6 C1 C2 C4
    // TODO: how to iterate only these, not full range ??
    // C (d5 + d2) eli C5 + C2 = parillinen
    if ( ((varC % 100000 - varC % 10000) / 10000 + (varC % 100 - varC % 10) / 10) % 2 === 0) {
        // A4 B2  B5 C3 A6 C2 A1
        latValue = Number(((varA % 10000 - varA % 1000) / 1000 * 10 + (varB % 100 - varB % 10) / 10 + (varB % 100000 - varB % 10000) / 10000 * 0.1 + (varC % 1000 - varC % 100) / 100 * 0.01 + (varA % 1000000 - varA % 100000) / 100000 * 0.001 + (varC % 100 - varC % 10) / 10 * 1.0E-4 + varA % 10 * 1.0E-5));
        // A5 C6 C1  B3 B6 A2 C5 B1
        lonValue = Number(((varA % 100000 - varA % 10000) / 10000 * 100 + (varC % 1000000 - varC % 100000) / 100000 * 10 + varC % 10 + (varB % 1000 - varB % 100) / 100 * 0.1 + (varB % 1000000 - varB % 100000) / 100000 * 0.01 + (varA % 100 - varA % 10) / 10 * 0.001 + (varC % 100000 - varC % 10000) / 10000 * 1.0E-4 + varB % 10 * 1.0E-5));
    }
    // C (d5 + d2) eli C5+C2= pariton
    else if ( ((varC % 100000 - varC % 10000) / 10000 + (varC % 100 - varC % 10) / 10) % 2 !== 0) {
        // B6 A1  A4 C6 C3 C2 A6
        latValue = Number(((varB % 1000000 - varB % 100000) / 100000 * 10 + varA % 10 + (varA % 10000 - varA % 1000) / 1000 * 0.1 + (varC % 1000000 - varC % 100000) / 100000 * 0.01 + (varC % 1000 - varC % 100) / 100 * 0.001 + (varC % 100 - varC % 10) / 10 * 1.0E-4 + (varA % 1000000 - varA % 100000) / 100000 * 1.0E-5))
        // B2 C1 A2  A5 B3 B1 C5 B5
        lonValue = Number(((varB % 100 - varB % 10) / 10 * 100 + varC % 10 * 10 + (varA % 100 - varA % 10) / 10 + (varA % 100000 - varA % 10000) / 10000 * 0.1 + (varB % 1000 - varB % 100) / 100 * 0.01 + varB % 10 * 0.001 + (varC % 100000 - varC % 10000) / 10000 * 1.0E-4 + (varB % 100000 - varB % 10000) / 10000 * 1.0E-5));
    }
    // A3 B4 C4 = ALWAYS ignore

    latValue = latSign * latValue;
    lonValue = lonSign * lonValue;

    return { lat: latValue, lon: lonValue }
}

function fix(v, left, right) {
    return (v < 0 ? '-' : ' ') + Math.abs(v).toFixed(right).padStart(left + right + 1, '0');
}

function haversineDistance(lat1,lon1,lat2,lon2) {
    const R = 6371e3; // metres
    const φ1 = lat1 * Math.PI/180; // φ, λ in radians
    const φ2 = lat2 * Math.PI/180;
    const Δφ = (lat2-lat1) * Math.PI/180;
    const Δλ = (lon2-lon1) * Math.PI/180;

    const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ/2) * Math.sin(Δλ/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    const d = R * c; // in metres
    return d;
  }

  function dm2dd( nsew, deg, min ) {
      let sign;
      if( nsew == 'N' || nsew == 'E' ) {
          sign = 1;
      }
      else {
          sign = -1;
      }

      let numdeg = parseInt(deg);
      let nummin = parseFloat(min);

      let dd = sign * (numdeg + nummin/60);
      return dd;
  }


function createShowSourceButton() {
    let mybutton = document.createElement("button");
    mybutton.innerHTML = "Source";
    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    noteref.appendChild(mybutton);

    mybutton.addEventListener("click", function() {
        var desc1 = document.getElementById("ctl00_ContentBody_ShortDescription").innerHTML;
        var desc2 = document.getElementById("ctl00_ContentBody_LongDescription").innerHTML;
        alert( desc1 + desc2 )
    });
}

function createShowSolveREReverseButton() {
    let mybutton = document.createElement("solvebutton");
    mybutton.innerHTML = "Solve RE";
    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    noteref.appendChild(mybutton);

    mybutton.addEventListener("click", function() {
        solveReverseRE();
    });

    /*
    mybutton.addEventListener("click", function() {
        $( function() {
            $( "#dialog" ).dialog({width: "auto"})
            .next(".ui-widget-overlay")
            .css({
                opacity: 1.0,
                filter: "Alpha(Opacity=100)",
                backgroundColor: "black"
            });
          } );
        solveReverseRE();
    });
    */
}

function createDialog( ) {
    let mydialog = document.createElement("div");
    mydialog.id = "dialog";
    mydialog.title = "Results";
    let resultmsg = document.createElement("p");
    resultmsg.id = "resultmsg";
    mydialog.appendChild(resultmsg);
//    let noteref = document.getElementsByClassName("minorCacheDetails Clear")[0];
    let noteref = document.getElementById("ctl00_ContentBody_detailWidget");
    noteref.appendChild(mydialog);
}

// TODO: definitely not the best practice, but works (for now)
function pollDOM() {
    const newCrd = document.getElementById('newCoordinates');

    if (newCrd != null) {
      // Do something with el
      newCrd.value = newCoordinates;

      let submitBtn = document.getElementsByClassName("btn-cc-parse")[0];
      //ssubmitBtn.click();
      } else {
      setTimeout(pollDOM, 300); // try again in 300 milliseconds
    }
}

function updateCoordinates( newCoords ) {
    let crd = document.getElementById("uxLatLonLink");
    crd.click();

    newCoordinates = newCoords;

    pollDOM();
    console.debug("XX: " + newCoords);
}

// retrieve bogus coordinates in ddm
// returns array [ lat side (N/S), lat deg, lat mindec, lon side (E/W), lon deg, lon mindec ];
function getBogusCoordsFromPage() {
    let coords = document.getElementById("uxLatLon").textContent.split(" ");
    if( coords.length != 6 ) {
        console.error("Error parsing coordinates");
        return;
    }
    return coords;
}

// convert latitude longitude array [ lat side (N/S), lat deg, lat mindec, lon side (E/W), lon deg, lon mindec ] to dd
// return { lat: latitude dd , lon: longitude dd }
function latLonDmArr2dd( latLonDmArray ) {
    // convert bogus coordinates from deg-mindec to deg-dec
    let bogusLat = dm2dd( latLonDmArray[0], latLonDmArray[1], latLonDmArray[2] );
    let bogusLon = dm2dd( latLonDmArray[3], latLonDmArray[4], latLonDmArray[5] );
    return { lat : bogusLat, lon : bogusLon }
}

// htmlsnip = html snippet in string
// replace = boolean; true = replace existing html fully
function addHTMLtoPage( htmlsnip, replace ) {
    let docEl = document.getElementsByClassName("Note Disclaimer")[0];

    if( replace ) {
        docEl.innerHTML = htmlsnip;
    }
    else {
        docEl.innerHTML = docEl.innerHTML + htmlsnip;
    }
}

/* async */ function solveReverseRE() {
    console.debug("Attempting to parse codes from note.");

    //let resultmsg = document.getElementById("resultmsg");
    let resultmsg = document.getElementsByClassName("Note Disclaimer")[0];


    let codes = document.getElementById("viewCacheNote").textContent.split('\n');
    for(let i=0; i<codes.length; i++) {
      codes[i] = codes[i].trim().split(" ");
    }
    if (codes.length != 3) {
        console.error("ERROR Parsing codes => Expected syntax: <code 1 from> <code 1 to> <code 1 regexp>\n <code 2 from> ...");
        return;
    }

    console.debug("Parsed: " + JSON.stringify(codes));

    let code1from = parseInt( codes[0][0] );
    let code1to = parseInt( codes[0][1] );

    let code2from = parseInt( codes[1][0] );
    let code2to = parseInt( codes[1][1] );

    let code3from = parseInt( codes[2][0] );
    let code3to = parseInt( codes[2][1] );

    let code1re = RegExp( codes[0][2] );
    let code2re = RegExp( codes[1][2] );
    let code3re = RegExp( codes[2][2] );

    let bogusLatLonArr = getBogusCoordsFromPage();
    // convert bogus coordinates from deg-min-dec to deg-dec
    let bogusDD = latLonDmArr2dd(bogusLatLonArr);
    let bogusLat = bogusDD.lat;
    let bogusLon = bogusDD.lon;

    console.debug("Parsed coords: " + JSON.stringify(bogusLatLonArr));
    console.debug("Converted coords: " + bogusLat + "  " + bogusLon);

    console.log("code1,code2,code3,lat(dd),lon(dd),lat(gps),lon(gps),dist(m)");
    let counter = 0;
    console.debug("Iterating [" + code1from.toString().padStart(6, "0") + " -> " + code1to.toString().padStart(6, "0") + "],["
                                + code2from.toString().padStart(6, "0") + " -> " + code2to.toString().padStart(6, "0") + "],["
                                + code3from.toString().padStart(6, "0") + " -> " + code3to.toString().padStart(6, "0") + "]");
                                // [001234->001235],[321333->421333],[444111->444222]
    for( let i = code1from; i <= code1to; i++ ) {
          let code1 = i.toString().padStart(6, "0");
          if( code1re.test( code1 ) == false ) {
            continue;
          }

          for( let j = code2from; j <= code2to; j++ ) {
            let code2 = j.toString().padStart(6, "0");
            if( code2re.test( code2 ) == false ) {
              continue;
            }

            console.debug("Testing [" + i.toString().padStart(6, "0") + "],[" + j.toString().padStart(6, "0") + "],[" + code3from.toString().padStart(6, "0") + " -> " + code3to.toString().padStart(6, "0") + "]" );
            for( let k = code3from; k <= code3to; k++ ) {
              let code3 = k.toString().padStart(6, "0");
              if( code3re.test( code3 ) == false ) {
                continue;
              }

              let ddlatlon = /* await */ code2LatLon( code1, code2, code3 );
              if( ddlatlon.lat == NaN || ddlatlon.lon == NaN ) continue;
              else {
                let dist = haversineDistance( bogusLat, bogusLon, ddlatlon.lat, ddlatlon.lon );

                if( dist < 3220 ) {
                    counter = counter+1;
                    let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                    let result = code1 + "," + code2 + "," + code3 + "," + fix(ddlatlon.lat, 2, 6) + "," + fix(ddlatlon.lon, 3, 6) + "," + dmlatlon.lat + "," + dmlatlon.lon + "," + dist.toFixed(0) ;
                    console.log(result);
                    resultmsg.innerText = resultmsg.innerText + "\n" + result;
                }
              }
            }
          }
      }
      console.log("Total " + counter + " matches found");
  }

function addBogusDDToPage() {
    let bogusLatLonArr = getBogusCoordsFromPage();
    // convert bogus coordinates from deg-min-dec to deg-dec
    let bogusDD = latLonDmArr2dd(bogusLatLonArr);
    let bogusLat = bogusDD.lat;
    let bogusLon = bogusDD.lon;

    console.debug("Parsed coords: " + JSON.stringify(bogusLatLonArr));
    console.debug("Converted coords: " + bogusLat + "  " + bogusLon);

    // add bogus dd coordinates on page
    addHTMLtoPage( "<strong> Bogus: " + bogusLat + "  " + bogusLon + " </strong>", true );

    /*let locnode = document.getElementById("ctl00_ContentBody_LocationSubPanel");
    locnode.textContent = bogusLat + "  " + bogusLon;
    locnode.appendChild( document.createElement("br"));
*/

}

// takes wigo codes as strings (may consist not numbers (variables) e.g. 022345 042x21 924ab9)
// returns resulting coordinate (DD) string
function evalPartialReverseWigo(codeA, codeB, codeC) {
    // validate that these are numbers
    console.debug("Input: " + codeA + ", " + codeB + ", " + codeC);
    if( isNaN( codeC[6-5] ) == false && isNaN( codeC[6-2] ) == false ) {
        // C5 + C2 = parillinen
        if( (Number(codeC[6-5]) + Number(codeC[6-2])) % 2 === 0 ) {
            console.debug("parillinen");
            // A4 B2  B5 C3 A6 C2 A1
            let resLat = "" + codeA[6-4] + codeB[6-2] + "." + codeB[6-5] + codeC[6-3] + codeA[6-6] + codeC[6-2] + codeA[6-1];
            // A5 C6 C1  B3 B6 A2 C5 B1
            let resLon = "" + codeA[6-5] + codeC[6-6] + codeC[6-1] + "." + codeB[6-3] + codeB[6-6] + codeA[6-2] + codeC[6-5] + codeB[6-1];
            console.debug(resLat + " " + resLon);
            return resLat + " " + resLon;
        }
        // C (d5 + d2) eli C5+C2= pariton
        else if ( (Number(codeC[6-5]) + Number(codeC[6-2])) %2 !== 0 ) {
            console.debug("pariton");
            // B6 A1  A4 C6 C3 C2 A6
            let resLat = "" + codeB[6-6] + codeA[6-1] + "." + codeA[6-4] + codeC[6-6] + codeC[6-3] + codeC[6-2] + codeA[6-6];
            // B2 C1 A2  A5 B3 B1 C5 B5
            let resLon = "" + codeB[6-2] + codeC[6-1] + codeA[6-2] + "." + codeA[6-5] + codeB[6-3] + codeB[6-1] + codeC[6-5] + codeB[6-5];
            console.debug(resLat + " " + resLon);
            return resLat + " " + resLon;

        }
    }
    else {
        console.debug("parillinen tai pariton");
        console.warn("Cannot deduce which equation to use - showing both results");
            let resLat1 = "" + codeA[6-4] + codeB[6-2] + "." + codeB[6-5] + codeC[6-3] + codeA[6-6] + codeC[6-2] + codeA[6-1];
            // A5 C6 C1  B3 B6 A2 C5 B1
            let resLon1 = "" + codeA[6-5] + codeC[6-6] + codeC[6-1] + "." + codeB[6-3] + codeB[6-6] + codeA[6-2] + codeC[6-5] + codeB[6-1];

            let resLat2 = "" + codeB[6-6] + codeA[6-1] + "." + codeA[6-4] + codeC[6-6] + codeC[6-3] + codeC[6-2] + codeA[6-6];
            // B2 C1 A2  A5 B3 B1 C5 B5
            let resLon2 = "" + codeB[6-2] + codeC[6-1] + codeA[6-2] + "." + codeA[6-5] + codeB[6-3] + codeB[6-1] + codeC[6-5] + codeB[6-5];
            console.debug( resLat1 + " " + resLon1 + "\n OR \n" + resLat2 + " " + resLon2 );
            return resLat1 + " " + resLon1 + " OR " + resLat2 + " " + resLon2;
    }
    // A3 B4 C4 = ALWAYS ignore

}

  (/* async */ function() {
    'use strict';
    let from, result;

    let cacheType = document.getElementsByClassName("cacheImage")[0].title;

    createShowSourceButton();
    addBogusDDToPage();



    if( cacheType == "Wherigo Cache" ) {
        createShowSolveREReverseButton();
        createDialog();

        // 1. parse from note
        let re1 = /RWIGO:/g;
        let re2 = /[0-9A-Za-z]{6}/g;
        console.log("Trying to find codes from user note:");
        let text = document.getElementById("viewCacheNote").innerText;
        if( text.match( re1 ) ) {
            console.debug("Match: Reverse:");
            let codes = text.match( re2 );
            console.debug( JSON.stringify( codes ) );

            if( codes !== null && (codes.length == 3 || codes.length == 6 || codes.length == 9) ) {
                console.debug("Match: " + codes[0] + "," + codes[1] + "," + codes[2] );

                let reverseRes = evalPartialReverseWigo( codes[0], codes[1], codes[2] );
                addHTMLtoPage("<br><strong>Reverse results = "+reverseRes+"</strong>", false);

                let ddlatlon = /* await */ code2LatLon( codes[0], codes[1], codes[2] );
                let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                from = codes[0] + " " + codes[1] + " " + codes[2] + " => ";
                let resultDD = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6);
                let resultDMD = dmlatlon.lat + " " + dmlatlon.lon;
                result = resultDMD;
                console.log( from + " " + resultDD + " = " + resultDMD );

                addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>", false);
                updateCoordinates( resultDMD );
            }
            else {
                console.warn("Error parsing coordinates from note");
            }
        }
        else {
            console.log("Codes not found from note - trying to find from UserSuppliedContent");
            let re = /[0-9]{6}/g;
            let codes = document.getElementsByClassName("UserSuppliedContent")[1].innerText.match( re );
            if( codes !== null && (codes.length == 3 || codes.length == 6 || codes.length == 9) ) {
                console.debug("Parsed codes from UserSuppliedContent: " + JSON.stringify(codes) );

                let reverseRes = evalPartialReverseWigo( codes[0], codes[1], codes[2] );
                addHTMLtoPage("<br><strong>Reverse results = "+reverseRes+"</strong>", false);

                let ddlatlon = /* await */ code2LatLon( codes[0], codes[1], codes[2] );
                let dmlatlon = /* await */ dd2dm( ddlatlon.lat, ddlatlon.lon);
                from = codes[0] + " " + codes[1] + " " + codes[2] + " => ";
                let resultDD = fix(ddlatlon.lat, 2, 6) + " " + fix(ddlatlon.lon, 3, 6);
                let resultDMD = dmlatlon.lat + " " + dmlatlon.lon;
                result = resultDMD;
                console.debug( from + " " + resultDD + " = " + resultDMD );

                addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>", false);
                updateCoordinates( resultDMD );
            }
            else {
                result = "Error: Three reverse wherigo codes not found";
                console.log( result );
                addHTMLtoPage("<br><strong> Reversewigo final coordinates =  " + result + " </strong>", false);
            }
        }

    }
})();