Skip to content

Instantly share code, notes, and snippets.

@mitchellrj
Last active June 4, 2018 16:52
Show Gist options
  • Save mitchellrj/23996aef648cf1901b7e1e765738b5d6 to your computer and use it in GitHub Desktop.
Save mitchellrj/23996aef648cf1901b7e1e765738b5d6 to your computer and use it in GitHub Desktop.
RFC6225 value calculator

I have no idea if this is a correct implementation or not, as I have no reference implementation to compare it to. I swear there used to be an implementation as part of Android but I can't find it now.

<!doctype html>
<html>
<head>
<title>RFC6225 / RFC3825 calculator</title>
<script>
const AltitudeTypes = {
NO_KNOWN_ALTITUDE: 0,
ALTITUDE_IN_METERS: 1,
ALTITUDE_IN_FLOORS: 2
};
const Datum = {
WGS84: 1,
NAD83_NAVD88: 2,
NAD83_MLLW: 3
};
const FIXED_POINT_INTEGRAL_BITS = 9;
const FIXED_POINT_FRACTIONAL_BITS = 25;
const FIXED_POINT_SIGNED = true;
const geoLocVersion = 1;
function toFixedPointBytes(i, integralBits, fractionBits, signed) {
const negative = i < 0;
var resultBytes = [];
var j = 0;
// the number of bytes that have been requested of us
const byteCount = Math.ceil((integralBits + fractionBits) / 8);
// the number of bytes needed to represent the result
var bytesAvail = 0;
// this may well be bigger than Number.MAX_SAFE_INTEGER.
var total = 0;
if (this.BigInt !== undefined) {
total = BigInt(Math.abs(i)) * BigInt(1 << fractionBits);
} else if (Math.log2(Number.MAX_SAFE_INTEGER) < byteCount) {
throw new Error("Cannot convert to fixed point format");
} else {
total = Math.round(Math.abs(i) * (1 << fractionBits));
}
bytesAvail = Math.ceil(Math.log2(total) / 8);
// Iterate only over the bytes available first, then pad with zeros;
// JavaScript numbers overflow otherwise.
for (j=0; j < bytesAvail; j++) {
resultBytes.unshift((total >> (8 * j)) & 0xff);
}
for (j=0; j < (byteCount - bytesAvail); j++) {
resultBytes.unshift(0);
}
if (signed && negative) {
for (j=0; j < byteCount; j++) {
resultBytes[j] ^= 0xff;
}
// TODO: this totally fails if the last byte is already 255.
resultBytes[byteCount - 1] += 1;
}
return resultBytes;
}
function geoLocBytes(latUnc, lat, lonUnc, lon, aType, altUnc, alt, datum) {
const latBytes = toFixedPointBytes(lat, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED);
const lonBytes = toFixedPointBytes(lon, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED);
return [
(latUnc << 2) | latBytes[0], // latunc (6 bits) + latitude (2 bits)
latBytes[1],
latBytes[2],
latBytes[3],
latBytes[4],
(lonUnc << 2) | lonBytes[0], // lonunc (6 bits) + longitude (2 bits)
lonBytes[1],
lonBytes[2],
lonBytes[3],
lonBytes[4],
(aType << 4) | (altUnc >> 2), // atype (4 bits) + altunc (4 bits)
((altUnc << 6) & 0xff) | ((alt >> 24) & 0x3f), // altunc (2 bits), alt (6 bits)
((alt >> 16) & 0xff),
((alt >> 8) & 0xff),
alt & 0xff,
(geoLocVersion << 6) | datum & 0x07 // version (2 bits), reserved (3 bits), datum (3 bits)
];
}
function geoConfBytes(laRes, lat, lonRes, lon, aType, altRes, alt, datum) {
const latBytes = toFixedPointBytes(lat, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED);
const lonBytes = toFixedPointBytes(lon, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED);
return [
(laRes << 2) | latBytes[0], // latRes (6 bits) + latitude (2 bits)
latBytes[1],
latBytes[2],
latBytes[3],
latBytes[4],
(lonRes << 2) | lonBytes[0], // lonRes (6 bits) + longitude (2 bits)
lonBytes[1],
lonBytes[2],
lonBytes[3],
lonBytes[4],
(aType << 4) | (altRes >> 2), // atype (4 bits) + altRes (4 bits)
((altRes << 6) & 0xff) | ((alt >> 24) & 0x3f), // altRes (2 bits), alt (6 bits)
((alt >> 16) & 0xff),
((alt >> 8) & 0xff),
alt & 0xff,
datum & 0x07 // reserved (5 bits), datum (3 bits)
];
}
// Resolution: number of bits of the lat / lon values that are considered
// valid.
function getResolution(min, max) {
const minBytes = toFixedPointBytes(min, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED);
const maxBytes = toFixedPointBytes(max, FIXED_POINT_INTEGRAL_BITS, FIXED_POINT_FRACTIONAL_BITS, FIXED_POINT_SIGNED);
var i = 0, j = 0;
var res = 0;
for (i; i < minBytes.length; i++) {
if (minBytes[i] === maxBytes[i]) {
res += 8;
} else {
for (j = 1; j < 8; j++) {
if ((minBytes[i] >> j) === (maxBytes[i] >> j)) {
res += j;
break
}
}
break;
}
}
return res;
}
// Uncertainty: 0 = uncertainty unknown, > 34 = reserved
function getUncertainty(center, min, max) {
return 8 - Math.ceil(Math.log2(Math.max(center - min, max - center)));
}
function getResults(lat, latMin, latMax, lon, lonMin, lonMax, aType, alt, altMin, altMax, datum) {
const latRes = getResolution(latMin, latMax);
const latUnc = getUncertainty(lat, latMin, latMax);
const lonRes = getResolution(lonMin, lonMax);
const lonUnc = getUncertainty(lon, lonMin, lonMax);
var altRes = 30;
var altUnc = 0;
var alt = 0;
if (aType === 1) {
var alt = (altMin + altMax) / 2;
var altRes = getResolution(altMin, altMax);
var altUnc = getUncertainty(alt, altMin, altMax);
}
return [
geoConfBytes(latRes, lat, lonRes, lon, aType, altRes, alt, datum),
geoLocBytes(latUnc, lat, lonUnc, lon, aType, altUnc, alt, datum)
];
}
</script>
<style>
label, input { display: block; }
</style>
</head>
<body>
<h1>RFC6225 / RFC3825 calculcator</h1>
<form id="generator">
<label for="latitude">Latitude (center):</label><input type="latitude" id="latitude" />
<label for="latitude-min">Latitude (min):</label><input type="latitude" id="latitude-min" />
<label for="latitude-max">Latitude (max):</label><input type="latitude" id="latitude-max" />
<label for="longitude">Longitude (center):</label><input type="longitude" id="longitude" />
<label for="longitude-min">Longitude (min):</label><input type="longitude" id="longitude-min" />
<label for="longitude-max">Longitude (max):</label><input type="longitude" id="longitude-max" />
<label for="altitude-type">Altitude type:</label><select id="altitude-type"><option value="0">Unknown</option><option value="1">Meters</option><option value="2">Floors</option></select>
<label for="altitude">Altitude</label><input type="number" id="altitude" />
<label for="altitude-min">Altitude (min):</label><input type="number" id="altitude-min" />
<label for="altitude-max">Altitude (max):</label><input type="number" id="altitude-max" />
<label for="coordinate-system">Co-ordinate system:</label><select id="coordinate-system"><option value="1">WGS84</option><option value="2">NAD83 + NAVD88</option><option value="3">NAD83 + MLLW</option></select>
<label for="with-option-header">With option header?</label><input id="with-option-header" type="checkbox" />
<input type="submit" value="Generate" />
<label for="result-dhcpv4-geoconf">DHCP v4 GeoConf option (123):</label> <input editable="false" id="result-dhcpv4-geoconf" />
<label for="result-dhcpv4-geoloc">DHCP v4 GeoLoc option (144):</label> <input editable="false" id="result-dhcpv4-geoloc" />
<label for="result-dhcpv6">DHCP v6 option (63):</label> <input editable="false" id="result-dhcpv6" />
</form>
<script>
(function () {
const form = document.getElementById('generator');
const lat = document.getElementById('latitude');
const latMin = document.getElementById('latitude-min');
const latMax = document.getElementById('latitude-max');
const lon = document.getElementById('longitude');
const lonMin = document.getElementById('longitude-min');
const lonMax = document.getElementById('longitude-max');
const aType = document.getElementById('altitude-type');
const alt = document.getElementById('altitude');
const altMin = document.getElementById('altitude-min');
const altMax = document.getElementById('altitude-max');
const datum = document.getElementById('coordinate-system');
const withOptionHeader = document.getElementById('with-option-header');
const resultDHCPv4GeoConf = document.getElementById('result-dhcpv4-geoconf');
const resultDHCPv4GeoLoc = document.getElementById('result-dhcpv4-geoloc');
const resultDHCPv6 = document.getElementById('result-dhcpv6');
form.addEventListener('submit', function (ev) {
ev.preventDefault();
var i = 0;
const results = getResults(
parseFloat(lat.value),
parseFloat(latMin.value),
parseFloat(latMax.value),
parseFloat(lon.value),
parseFloat(lonMin.value),
parseFloat(lonMax.value),
parseInt(aType.value, 10),
parseFloat(alt.value),
parseFloat(altMin.value),
parseFloat(altMax.value),
parseInt(datum.value, 10),
);
resultDHCPv4GeoConf.value = "";
resultDHCPv4GeoLoc.value = "";
resultDHCPv6.value = "";
if (withOptionHeader.checked) {
resultDHCPv4GeoConf.value += '00:7b:00:10:';
resultDHCPv4GeoLoc.value += '00:90:00:10:';
resultDHCPv6.value += '00:3f:00:10:';
}
resultDHCPv4GeoConf.value += results[0].map(function(n) { return ('00' + n.toString(16)).slice(-2); }).join(':');
resultDHCPv4GeoLoc.value += results[1].map(function(n) { return ('00' + n.toString(16)).slice(-2); }).join(':');
resultDHCPv6.value += results[1].map(function(n) { return ('00' + n.toString(16)).slice(-2); }).join(':');
return false;
});
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment