/* Copyright (c) 2023-forever Douglas Malnati. All rights reserved. See the /faq/tos page for details. (If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.) */ let DEBUG = false; function Gather(str) { if (DEBUG) { console.log(str); } return str + "\n"; } export class WSPREncoded { static EnableDebug() { DEBUG = true; } static DisableDebug() { DEBUG = false; } static DBM_POWER_LIST = [ 0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40, 43, 47, 50, 53, 57, 60 ]; static EncodeNumToPower(num) { if (num < 0 || WSPREncoded.DBM_POWER_LIST.length - 1 < num) { num = 0; } return WSPREncoded.DBM_POWER_LIST[num]; } static DecodePowerToNum(power) { let powerVal = WSPREncoded.DBM_POWER_LIST.indexOf(power); powerVal = (powerVal == -1) ? 0 : powerVal; return powerVal; } static EncodeBase36(val) { let retVal; if (val < 10) { retVal = String.fromCharCode("0".charCodeAt(0) + val); } else { retVal = String.fromCharCode("A".charCodeAt(0) + (val - 10)); } return retVal; } static DecodeBase36(c) { let retVal = 0; let cVal = c.charCodeAt(0); let aVal = "A".charCodeAt(0); let zVal = "Z".charCodeAt(0); let zeroVal = "0".charCodeAt(0); if (aVal <= cVal && cVal <= zVal) { retVal = 10 + (cVal - aVal); } else { retVal = cVal - zeroVal; } return retVal; } static DecodeMaidenheadToDeg(grid, opts = {}) { let snap = opts.snap ?? "center"; grid = grid.toUpperCase(); let lat = 0; let lng = 0; if (grid.length >= 2) { let g1 = grid.charAt(0); let g2 = grid.charAt(1); lng += (g1.charCodeAt(0) - "A".charCodeAt(0)) * 200000; lat += (g2.charCodeAt(0) - "A".charCodeAt(0)) * 100000; } if (grid.length >= 4) { let g3 = grid.charAt(2); let g4 = grid.charAt(3); lng += (g3.charCodeAt(0) - "0".charCodeAt(0)) * 20000; lat += (g4.charCodeAt(0) - "0".charCodeAt(0)) * 10000; } else { if (snap == "center") { // snap prior decoded resolution to be in the middle of the grid lng += 200000 / 2; lat += 100000 / 2; } } if (grid.length >= 6) { let g5 = grid.charAt(4); let g6 = grid.charAt(5); lng += (g5.charCodeAt(0) - "A".charCodeAt(0)) * 834; lat += (g6.charCodeAt(0) - "A".charCodeAt(0)) * 417; if (snap == "center") { // snap this decoded resolution to be in the middle of the grid lng += 834 / 2; lat += 417 / 2; } } else { if (snap == "center") { // snap prior decoded resolution to be in the middle of the grid lng += 20000 / 2; lat += 10000 / 2; } } lng -= (180 * 10000); lat -= ( 90 * 10000); lng *= 100; lat *= 100; lng /= 1000000; lat /= 1000000; return [lat, lng]; } static GetReferenceGrid4(lat, lng) { lat = Number(lat); lng = Number(lng); if (isNaN(lat) || isNaN(lng)) { throw new RangeError(`Location ${lat}, ${lng} is invalid.`); } if (lat < -90 || lat > 90 || lng < -180 || lng > 180) { throw new RangeError(`Location ${lat}, ${lng} is outside valid coordinate bounds.`); } if (lat == 90) { lat -= Number.EPSILON; } if (lng == 180) { lng -= Number.EPSILON; } let lngFromOrigin = lng + 180; let latFromOrigin = lat + 90; let fieldLngIdx = Math.floor(lngFromOrigin / 20); let fieldLatIdx = Math.floor(latFromOrigin / 10); let squareLngIdx = Math.floor((lngFromOrigin % 20) / 2); let squareLatIdx = Math.floor((latFromOrigin % 10) / 1); return "" + String.fromCharCode("A".charCodeAt(0) + fieldLngIdx) + String.fromCharCode("A".charCodeAt(0) + fieldLatIdx) + String.fromCharCode("0".charCodeAt(0) + squareLngIdx) + String.fromCharCode("0".charCodeAt(0) + squareLatIdx); } // https://stackoverflow.com/questions/32806084/google-map-zoom-parameter-in-url-not-working static MakeGoogleMapsLink(lat, lng) { // approx zoom levels // 1: World // 5: Landmass/continent // 10: City // 15: Streets // 20: Buildings let zoom = 4; return `https://maps.google.com/?q=${lat},${lng}&ll=${lat},${lng}&z=${zoom}`; } static EncodeU4BCall(id1, id3, grid56, altM) { let retVal = ""; // pick apart inputs let grid5 = grid56.substring(0, 1); let grid6 = grid56.substring(1); // convert inputs into components of a big number let grid5Val = grid5.charCodeAt(0) - "A".charCodeAt(0); let grid6Val = grid6.charCodeAt(0) - "A".charCodeAt(0); let altFracM = Math.round(altM / 20); retVal += Gather(`grid5Val(${grid5Val}), grid6Val(${grid6Val}), altFracM(${altFracM})`); // convert inputs into a big number let val = 0; val *= 24; val += grid5Val; val *= 24; val += grid6Val; val *= 1068; val += altFracM; retVal += Gather(`val(${val})`); // extract into altered dynamic base let id6Val = val % 26; val = Math.floor(val / 26); let id5Val = val % 26; val = Math.floor(val / 26); let id4Val = val % 26; val = Math.floor(val / 26); let id2Val = val % 36; val = Math.floor(val / 36); retVal += Gather(`id2Val(${id2Val}), id4Val(${id4Val}), id5Val(${id5Val}), id6Val(${id6Val})`); // convert to encoded callsign let id2 = WSPREncoded.EncodeBase36(id2Val); let id4 = String.fromCharCode("A".charCodeAt(0) + id4Val); let id5 = String.fromCharCode("A".charCodeAt(0) + id5Val); let id6 = String.fromCharCode("A".charCodeAt(0) + id6Val); let call = id1 + id2 + id3 + id4 + id5 + id6; retVal += Gather(`id1(${id1}), id2(${id2}), id3(${id3}), id4(${id4}), id5(${id5}), id6(${id6})`); retVal += Gather(`${call}`); retVal = call; return retVal; } static DecodeU4BCall(call) { let retVal = ""; // break call down let id2 = call.charAt(1); let id4 = call.charAt(3); let id5 = call.charAt(4); let id6 = call.charAt(5); // convert to values which are offset from 'A' let id2Val = WSPREncoded.DecodeBase36(id2); let id4Val = id4.charCodeAt(0) - "A".charCodeAt(0); let id5Val = id5.charCodeAt(0) - "A".charCodeAt(0); let id6Val = id6.charCodeAt(0) - "A".charCodeAt(0); retVal += Gather(`id2Val(${id2Val}), id4Val(${id4Val}), id5Val(${id5Val}), id6Val(${id6Val})`); // integer value to use to decode let val = 0; // combine values into single integer val *= 36; val += id2Val; val *= 26; val += id4Val; // spaces aren't used, so 26 not 27 val *= 26; val += id5Val; // spaces aren't used, so 26 not 27 val *= 26; val += id6Val; // spaces aren't used, so 26 not 27 retVal += Gather(`val ${val}`); // extract values let altFracM = val % 1068; val = Math.floor(val / 1068); let grid6Val = val % 24; val = Math.floor(val / 24); let grid5Val = val % 24; val = Math.floor(val / 24); let altM = altFracM * 20; let grid6 = String.fromCharCode(grid6Val + "A".charCodeAt(0)); let grid5 = String.fromCharCode(grid5Val + "A".charCodeAt(0)); let grid56 = grid5 + grid6; retVal += Gather(`grid ....${grid56} ; altM ${altM}`); retVal += Gather("-----------"); retVal = [grid56, altM]; return retVal; } static EncodeU4BGridPower(tempC, voltage, speedKnots, gpsValid) { // parse input presentations tempC = parseFloat(tempC); voltage = parseFloat(voltage); speedKnots = parseFloat(speedKnots); gpsValid = parseInt(gpsValid); let retVal = ""; // map input presentations onto input radix (numbers within their stated range of possibilities) let tempCNum = tempC - -50; let voltageNum = (Math.round(((voltage * 100) - 300) / 5) + 20) % 40; let speedKnotsNum = Math.round(speedKnots / 2.0); let gpsValidNum = gpsValid; retVal += Gather(`tempCNum(${tempCNum}), voltageNum(${voltageNum}), speedKnotsNum,(${speedKnotsNum}), gpsValidNum(${gpsValidNum})`); // shift inputs into a big number let val = 0; val *= 90; val += tempCNum; val *= 40; val += voltageNum; val *= 42; val += speedKnotsNum; val *= 2; val += gpsValidNum; val *= 2; val += 1; // standard telemetry retVal += Gather(`val(${val})`); // unshift big number into output radix values let powerVal = val % 19; val = Math.floor(val / 19); let g4Val = val % 10; val = Math.floor(val / 10); let g3Val = val % 10; val = Math.floor(val / 10); let g2Val = val % 18; val = Math.floor(val / 18); let g1Val = val % 18; val = Math.floor(val / 18); retVal += Gather(`grid1Val(${g1Val}), grid2Val(${g2Val}), grid3Val(${g3Val}), grid4Val(${g4Val})`); retVal += Gather(`powerVal(${powerVal})`); // map output radix to presentation let g1 = String.fromCharCode("A".charCodeAt(0) + g1Val); let g2 = String.fromCharCode("A".charCodeAt(0) + g2Val); let g3 = String.fromCharCode("0".charCodeAt(0) + g3Val); let g4 = String.fromCharCode("0".charCodeAt(0) + g4Val); let grid = g1 + g2 + g3 + g4; let power = WSPREncoded.EncodeNumToPower(powerVal); retVal += Gather(`grid(${grid}), g1(${g1}), g2(${g2}), g3(${g3}), g4(${g4})`); retVal += Gather(`power(${power})`); retVal += Gather(`${grid} ${power}`); retVal = [grid, power]; return retVal; } static DecodeU4BGridPower(grid, power) { let debug = ""; power = parseInt(power); let g1 = grid.charAt(0); let g2 = grid.charAt(1); let g3 = grid.charAt(2); let g4 = grid.charAt(3); let g1Val = g1.charCodeAt(0) - "A".charCodeAt(0); let g2Val = g2.charCodeAt(0) - "A".charCodeAt(0); let g3Val = g3.charCodeAt(0) - "0".charCodeAt(0); let g4Val = g4.charCodeAt(0) - "0".charCodeAt(0); let powerVal = WSPREncoded.DecodePowerToNum(power); let val = 0; val *= 18; val += g1Val; val *= 18; val += g2Val; val *= 10; val += g3Val; val *= 10; val += g4Val; val *= 19; val += powerVal; debug += Gather(`val(${val})`); let telemetryId = val % 2 ; val = Math.floor(val / 2); let bit2 = val % 2 ; val = Math.floor(val / 2); let speedKnotsNum = val % 42 ; val = Math.floor(val / 42); let voltageNum = val % 40 ; val = Math.floor(val / 40); let tempCNum = val % 90 ; val = Math.floor(val / 90); let retVal; if (telemetryId == 0) { let msgType = "extra"; let extraTelemSeq = bit2 == 0 ? "first" : "second"; retVal = { msgType: "extra", msgSeq : extraTelemSeq, }; } else { let msgType = "standard"; let gpsValid = bit2; let tempC = -50 + tempCNum; let voltage = 3.0 + (((voltageNum + 20) % 40) * 0.05); let speedKnots = speedKnotsNum * 2; let speedKph = speedKnots * 1.852; debug += Gather(`tempCNum(${tempCNum}), tempC(${tempC})`); debug += Gather(`voltageNum(${voltageNum}), voltage(${voltage})`); debug += Gather(`speedKnotsNum(${speedKnotsNum}), speedKnots(${speedKnots}), speedKph(${speedKph})`); debug += Gather(`gpsValid(${gpsValid})`); debug += Gather(`${tempC}, ${voltage}, ${speedKnots}, ${gpsValid}`); retVal = { msgType: msgType, data : [tempC, voltage, speedKnots, gpsValid], }; } retVal.debug = debug; return retVal; } }