240 lines
7.3 KiB
JavaScript
240 lines
7.3 KiB
JavaScript
(() => {
|
|
'use strict';
|
|
|
|
// GENERIC FUNCTIONS
|
|
|
|
// toTitle :: String -> String
|
|
let toTitle = s => s.length ? (s[0].toUpperCase() + s.slice(1)) : '';
|
|
|
|
// COMPASS DATA AND FUNCTIONS
|
|
|
|
// Scale invariant keys for points of the compass
|
|
// (allows us to look up a translation for one scale of compass (32 here)
|
|
// for use in another size of compass (8 or 16 points)
|
|
// (Also semi-serviceable as more or less legible keys without translation)
|
|
|
|
// compassKeys :: Int -> [String]
|
|
let compassKeys = depth => {
|
|
let urCompass = ['N', 'S', 'N'],
|
|
subdivision = (compass, n) => n <= 1 ? (
|
|
compass
|
|
) : subdivision( // Borders between N and S engender E and W.
|
|
// other new boxes concatenate their parent keys.
|
|
compass.reduce((a, x, i, xs) => {
|
|
if (i > 0) {
|
|
return (n === depth) ? (
|
|
a.concat([x === 'N' ? 'W' : 'E'], x)
|
|
) : a.concat([xs[i - 1] + x, x]);
|
|
} else return a.concat(x);
|
|
}, []),
|
|
n - 1
|
|
);
|
|
return subdivision(urCompass, depth)
|
|
.slice(0, -1);
|
|
};
|
|
|
|
// https://zh.wikipedia.org/wiki/%E7%BD%97%E7%9B%98%E6%96%B9%E4%BD%8D
|
|
let lstLangs = [{
|
|
'name': 'English',
|
|
expansions: {
|
|
N: 'north',
|
|
S: 'south',
|
|
E: 'east',
|
|
W: 'west',
|
|
b: ' by ',
|
|
'-': '-'
|
|
},
|
|
'N': 'N',
|
|
'NNNE': 'NbE',
|
|
'NNE': 'N-NE',
|
|
'NNENE': 'NEbN',
|
|
'NE': 'NE',
|
|
'NENEE': 'NEbE',
|
|
'NEE': 'E-NE',
|
|
'NEEE': 'EbN',
|
|
'E': 'E',
|
|
'EEES': 'EbS',
|
|
'EES': 'E-SE',
|
|
'EESES': 'SEbE',
|
|
'ES': 'SE',
|
|
'ESESS': 'SEbS',
|
|
'ESS': 'S-SE',
|
|
'ESSS': 'SbE',
|
|
'S': 'S',
|
|
'SSSW': 'SbW',
|
|
'SSW': 'S-SW',
|
|
'SSWSW': 'SWbS',
|
|
'SW': 'SW',
|
|
'SWSWW': 'SWbW',
|
|
'SWW': 'W-SW',
|
|
'SWWW': 'WbS',
|
|
'W': 'W',
|
|
'WWWN': 'WbN',
|
|
'WWN': 'W-NW',
|
|
'WWNWN': 'NWbW',
|
|
'WN': 'NW',
|
|
'WNWNN': 'NWbN',
|
|
'WNN': 'N-NW',
|
|
'WNNN': 'NbW'
|
|
}, {
|
|
'name': 'Chinese',
|
|
'N': '北',
|
|
'NNNE': '北微东',
|
|
'NNE': '东北偏北',
|
|
'NNENE': '东北微北',
|
|
'NE': '东北',
|
|
'NENEE': '东北微东',
|
|
'NEE': '东北偏东',
|
|
'NEEE': '东微北',
|
|
'E': '东',
|
|
'EEES': '东微南',
|
|
'EES': '东南偏东',
|
|
'EESES': '东南微东',
|
|
'ES': '东南',
|
|
'ESESS': '东南微南',
|
|
'ESS': '东南偏南',
|
|
'ESSS': '南微东',
|
|
'S': '南',
|
|
'SSSW': '南微西',
|
|
'SSW': '西南偏南',
|
|
'SSWSW': '西南微南',
|
|
'SW': '西南',
|
|
'SWSWW': '西南微西',
|
|
'SWW': '西南偏西',
|
|
'SWWW': '西微南',
|
|
'W': '西',
|
|
'WWWN': '西微北',
|
|
'WWN': '西北偏西',
|
|
'WWNWN': '西北微西',
|
|
'WN': '西北',
|
|
'WNWNN': '西北微北',
|
|
'WNN': '西北偏北',
|
|
'WNNN': '北微西'
|
|
}];
|
|
|
|
// pointIndex :: Int -> Num -> Int
|
|
let pointIndex = (power, degrees) => {
|
|
let nBoxes = (power ? Math.pow(2, power) : 32);
|
|
return Math.ceil(
|
|
(degrees + (360 / (nBoxes * 2))) % 360 * nBoxes / 360
|
|
) || 1;
|
|
};
|
|
|
|
// pointNames :: Int -> Int -> [String]
|
|
let pointNames = (precision, iBox) => {
|
|
let k = compassKeys(precision)[iBox - 1];
|
|
return lstLangs.map(dctLang => {
|
|
let s = dctLang[k] || k, // fallback to key if no translation
|
|
dctEx = dctLang.expansions;
|
|
|
|
return dctEx ? toTitle(s.split('')
|
|
.map(c => dctEx[c])
|
|
.join(precision > 5 ? ' ' : ''))
|
|
.replace(/ /g, ' ') : s;
|
|
});
|
|
};
|
|
|
|
// maximumBy :: (a -> a -> Ordering) -> [a] -> a
|
|
let maximumBy = (f, xs) =>
|
|
xs.reduce((a, x) => a === undefined ? x : (
|
|
f(x, a) > 0 ? x : a
|
|
), undefined);
|
|
|
|
// justifyLeft :: Int -> Char -> Text -> Text
|
|
let justifyLeft = (n, cFiller, strText) =>
|
|
n > strText.length ? (
|
|
(strText + replicate(n, cFiller)
|
|
.join(''))
|
|
.substr(0, n)
|
|
) : strText;
|
|
|
|
// justifyRight :: Int -> Char -> Text -> Text
|
|
let justifyRight = (n, cFiller, strText) =>
|
|
n > strText.length ? (
|
|
(replicate(n, cFiller)
|
|
.join('') + strText)
|
|
.slice(-n)
|
|
) : strText;
|
|
|
|
// replicate :: Int -> a -> [a]
|
|
let replicate = (n, a) => {
|
|
let v = [a],
|
|
o = [];
|
|
if (n < 1) return o;
|
|
while (n > 1) {
|
|
if (n & 1) o = o.concat(v);
|
|
n >>= 1;
|
|
v = v.concat(v);
|
|
}
|
|
return o.concat(v);
|
|
};
|
|
|
|
// transpose :: [[a]] -> [[a]]
|
|
let transpose = xs =>
|
|
xs[0].map((_, iCol) => xs.map((row) => row[iCol]));
|
|
|
|
// length :: [a] -> Int
|
|
// length :: Text -> Int
|
|
let length = xs => xs.length;
|
|
|
|
// compareByLength = (a, a) -> (-1 | 0 | 1)
|
|
let compareByLength = (a, b) => {
|
|
let [na, nb] = [a, b].map(length);
|
|
return na < nb ? -1 : na > nb ? 1 : 0;
|
|
};
|
|
|
|
// maxLen :: [String] -> Int
|
|
let maxLen = xs => maximumBy(compareByLength, xs)
|
|
.length;
|
|
|
|
// compassTable :: Int -> [Num] -> Maybe String
|
|
let compassTable = (precision, xs) => {
|
|
if (precision < 1) return undefined;
|
|
else {
|
|
let intPad = 2;
|
|
|
|
let lstIndex = xs.map(x => pointIndex(precision, x)),
|
|
lstStrIndex = lstIndex.map(x => x.toString()),
|
|
nIndexWidth = maxLen(lstStrIndex),
|
|
colIndex = lstStrIndex.map(
|
|
x => justifyRight(nIndexWidth, ' ', x)
|
|
);
|
|
|
|
let lstAngles = xs.map(x => x.toFixed(2) + '°'),
|
|
nAngleWidth = maxLen(lstAngles) + intPad,
|
|
colAngles = lstAngles.map(x => justifyRight(nAngleWidth, ' ', x));
|
|
|
|
let lstTrans = transpose(
|
|
lstIndex.map(i => pointNames(precision, i))
|
|
),
|
|
lstTransWidths = lstTrans.map(x => maxLen(x) + 2),
|
|
colsTrans = lstTrans
|
|
.map((lstLang, i) => lstLang
|
|
.map(x => justifyLeft(lstTransWidths[i], ' ', x))
|
|
);
|
|
|
|
return transpose([colIndex]
|
|
.concat([colAngles], [replicate(lstIndex.length, " ")])
|
|
.concat(colsTrans))
|
|
.map(x => x.join(''))
|
|
.join('\n');
|
|
}
|
|
}
|
|
|
|
// TEST
|
|
let xs = [0.0, 16.87, 16.88, 33.75, 50.62, 50.63, 67.5, 84.37,
|
|
84.38, 101.25, 118.12, 118.13, 135.0, 151.87, 151.88, 168.75,
|
|
185.62, 185.63, 202.5, 219.37, 219.38, 236.25, 253.12, 253.13,
|
|
270.0, 286.87, 286.88, 303.75, 320.62, 320.63, 337.5, 354.37,
|
|
354.38
|
|
];
|
|
|
|
// If we supply other precisions, like 4 or 6, (2^n -> 16 or 64 boxes)
|
|
// the bearings will be divided amongst smaller or larger numbers of boxes,
|
|
// either using name translations retrieved by the generic hash
|
|
// or using the hash itself (combined with any expansions)
|
|
// to substitute for missing names for very finely divided boxes.
|
|
|
|
return compassTable(5, xs); // 2^5 -> 32 boxes
|
|
})();
|