diff --git a/src/dct/libs/utils.lua b/src/dct/libs/utils.lua index 3d365d18..b5699809 100644 --- a/src/dct/libs/utils.lua +++ b/src/dct/libs/utils.lua @@ -232,141 +232,6 @@ function utils.centroid2D(point, pcentroid, n) return vector.Vector2D(c), n1 end -utils.posfmt = { - ["DD"] = 1, - ["DDM"] = 2, - ["DMS"] = 3, - ["MGRS"] = 4, -} - --- reduce the accuracy of the position to the precision specified -function utils.degradeLL(lat, long, precision) - local multiplier = math.pow(10, precision) - lat = math.modf(lat * multiplier) / multiplier - long = math.modf(long * multiplier) / multiplier - return lat, long -end - --- set up formatting args for the LL string -local function getLLformatstr(precision, fmt) - local decimals = precision - if fmt == utils.posfmt.DDM then - if precision > 1 then - decimals = precision - 1 - else - decimals = 0 - end - elseif fmt == utils.posfmt.DMS then - if precision > 4 then - decimals = precision - 2 - elseif precision > 2 then - decimals = precision - 3 - else - decimals = 0 - end - end - if decimals == 0 then - return "%02.0f" - else - return "%0"..(decimals+3).."."..decimals.."f" - end -end - -function utils.LLtostring(lat, long, precision, fmt) - local northing = "N" - local easting = "E" - local degsym = '°' - - if lat < 0 then - northing = "S" - end - - if long < 0 then - easting = "W" - end - - lat, long = utils.degradeLL(lat, long, precision) - lat = math.abs(lat) - long = math.abs(long) - - local fmtstr = getLLformatstr(precision, fmt) - - if fmt == utils.posfmt.DD then - return string.format(fmtstr..degsym, lat)..northing.. - " ".. - string.format(fmtstr..degsym, long)..easting - end - - -- we give the minutes and seconds a little push in case the division - -- from the truncation with this multiplication gives us a value ending - -- in .99999... - local tolerance = 1e-8 - - local latdeg = math.floor(lat) - local latmind = (lat - latdeg)*60 + tolerance - local longdeg = math.floor(long) - local longmind = (long - longdeg)*60 + tolerance - - if fmt == utils.posfmt.DDM then - return string.format("%02d"..degsym..fmtstr.."'", latdeg, latmind).. - northing.. - " ".. - string.format("%03d"..degsym..fmtstr.."'", longdeg, longmind).. - easting - end - - local latmin = math.floor(latmind) - local latsecd = (latmind - latmin)*60 + tolerance - local longmin = math.floor(longmind) - local longsecd = (longmind - longmin)*60 + tolerance - - return string.format("%02d"..degsym.."%02d'"..fmtstr.."\"", - latdeg, latmin, latsecd).. - northing.. - " ".. - string.format("%03d"..degsym.."%02d'"..fmtstr.."\"", - longdeg, longmin, longsecd).. - easting -end - -function utils.MGRStostring(mgrs, precision) - local str = mgrs.UTMZone .. " " .. mgrs.MGRSDigraph - - if precision == 0 then - return str - end - - local divisor = 10^(5-precision) - local fmtstr = "%0"..precision.."d" - - if precision == 0 then - return str - end - - return str.." "..string.format(fmtstr, (mgrs.Easting/divisor)).. - " "..string.format(fmtstr, (mgrs.Northing/divisor)) -end - -function utils.degrade_position(position, precision) - local lat, long = coord.LOtoLL(position) - lat, long = utils.degradeLL(lat, long, precision) - return coord.LLtoLO(lat, long, 0) -end - -function utils.fmtposition(position, precision, fmt) - precision = math.floor(precision) - assert(precision >= 0 and precision <= 5, - "value error: precision range [0,5]") - local lat, long = coord.LOtoLL(position) - - if fmt == utils.posfmt.MGRS then - return utils.MGRStostring(coord.LLtoMGRS(lat, long), - precision) - end - - return utils.LLtostring(lat, long, precision, fmt) -end - function utils.trimTypeName(typename) if typename ~= nil then return string.match(typename, "[^.]-$") diff --git a/src/dct/settings/theater.lua b/src/dct/settings/theater.lua index a33f1ae2..c5f9466c 100644 --- a/src/dct/settings/theater.lua +++ b/src/dct/settings/theater.lua @@ -7,6 +7,7 @@ local utils = require("libs.utils") local enum = require("dct.enum") local dctutils = require("dct.libs.utils") +local uihuman = require("dct.ui.human") local function validate_weapon_restrictions(cfgdata, tbl) local path = cfgdata.file @@ -86,7 +87,7 @@ local function gridfmt_transform(tbl) if type(v) == "number" then ntbl[k] = v else - ntbl[k] = dctutils.posfmt[string.upper(v)] + ntbl[k] = uihuman.posfmt[string.upper(v)] assert(ntbl[k] ~= nil, "invalid grid format for "..k) end end @@ -190,18 +191,18 @@ local function theatercfgs(config) ["default"] = { ["gridfmt"] = { -- default is DMS, no need to list - ["Ka-50"] = dctutils.posfmt.DDM, - ["Mi-8MT"] = dctutils.posfmt.DDM, - ["SA342M"] = dctutils.posfmt.DDM, - ["SA342L"] = dctutils.posfmt.DDM, - ["UH-1H"] = dctutils.posfmt.DDM, - ["A-10A"] = dctutils.posfmt.MGRS, - ["A-10C"] = dctutils.posfmt.MGRS, - ["A-10C_2"] = dctutils.posfmt.MGRS, - ["F-5E-3"] = dctutils.posfmt.DDM, - ["F-16C_50"] = dctutils.posfmt.DDM, - ["FA-18C_hornet"] = dctutils.posfmt.DDM, - ["M-2000C"] = dctutils.posfmt.DDM, + ["Ka-50"] = uihuman.posfmt.DDM, + ["Mi-8MT"] = uihuman.posfmt.DDM, + ["SA342M"] = uihuman.posfmt.DDM, + ["SA342L"] = uihuman.posfmt.DDM, + ["UH-1H"] = uihuman.posfmt.DDM, + ["A-10A"] = uihuman.posfmt.MGRS, + ["A-10C"] = uihuman.posfmt.MGRS, + ["A-10C_2"] = uihuman.posfmt.MGRS, + ["F-5E-3"] = uihuman.posfmt.DDM, + ["F-16C_50"] = uihuman.posfmt.DDM, + ["FA-18C_hornet"] = uihuman.posfmt.DDM, + ["M-2000C"] = uihuman.posfmt.DDM, }, ["ato"] = {}, }, diff --git a/src/dct/ui/human.lua b/src/dct/ui/human.lua index 5ce11d21..0d951f49 100644 --- a/src/dct/ui/human.lua +++ b/src/dct/ui/human.lua @@ -9,7 +9,85 @@ local dctutils = require("dct.libs.utils") local Mission = require("dct.libs.Mission") local WS = require("dct.assets.worldstate") -local human = {} +local function conversion_entry(ratio, symbol) + return { + ["ratio"] = ratio, + ["symbol"] = symbol, + } +end + +local posfmt = { + ["DD"] = 1, + ["DDM"] = 2, + ["DMS"] = 3, + ["MGRS"] = 4, +} + +local altfmt = { + ["FEET"] = 1, + ["METER"] = 2, +} + +--- altitude conversion table from meters to X +local altitude_conversion = { + [altfmt.FEET] = conversion_entry(3.28084, "ft"), + [altfmt.METER] = conversion_entry(1, "m"), +} + +local pressurefmt = { + ["INHG"] = 1, + ["MMHG"] = 2, + ["HPA"] = 3, + ["MBAR"] = 4, +} + +--- pressure conversion table from pascals to X +local pressure_conversion = { + [pressurefmt.INHG] = conversion_entry(0.0002953, "inHg"), + [pressurefmt.MMHG] = conversion_entry(0.00750062, "mmHg"), + [pressurefmt.HPA] = conversion_entry(0.01, "hPa"), + [pressurefmt.MBAR] = conversion_entry(0.1, "mbar"), +} + +local distancefmt = { + ["NAUTICALMILE"] = 1, + ["STATUTEMILE"] = 2, + ["KILOMETER"] = 3, +} + +--- distance conversion from meters to X +local distance_conversion = { + [distancefmt.NAUTICALMILE] = conversion_entry(0.000539957, "NM"), + [distancefmt.STATUTEMILE] = conversion_entry(0.000621371, "sm"), + [distancefmt.KILOMETER] = conversion_entry(0.001, "km"), +} + +local speedfmt = { + ["KNOTS"] = 1, + ["KPH"] = 2, + ["MPH"] = 3, +} + +-- converts meters per second to X speed +local speed_conversion = { + [speedfmt.KNOTS] = conversion_entry(1.94384, "kts"), + [speedfmt.KPH] = conversion_entry(3.6, "kph"), + [speedfmt.MPH] = conversion_entry(2.23694, "mph"), +} + +local unitstype = { + ["SPEED"] = 1, + ["DISTANCE"] = 2, + ["ALTITUDE"] = 3, + ["PRESSURE"] = 4, +} + +local conversiontbl = { + [unitstype.SPEED] = speed_conversion, + [unitstype.DISTANCE] = distance_conversion, + [unitstype.ALTITUDE] = altitude_conversion, + [unitstype.PRESSURE] = pressure_conversion, +} --- Filter out facts that are not considered targets. local function istarget(owner) @@ -38,6 +116,63 @@ local function isthreat(owner) end end +--- reduce the accuracy of the position to the precision specified +local function degradeLL(lat, long, precision) + local multiplier = math.pow(10, precision) + lat = math.modf(lat * multiplier) / multiplier + long = math.modf(long * multiplier) / multiplier + return lat, long +end + +--- set up formatting args for the LL string +local function getLLformatstr(precision, fmt) + local decimals = precision + if fmt == posfmt.DDM then + if precision > 1 then + decimals = precision - 1 + else + decimals = 0 + end + elseif fmt == posfmt.DMS then + if precision > 4 then + decimals = precision - 2 + elseif precision > 2 then + decimals = precision - 3 + else + decimals = 0 + end + end + if decimals == 0 then + return "%02.0f" + else + return "%0"..(decimals+3).."."..decimals.."f" + end +end + +local human = {} + +human.posfmt = posfmt +human.altfmt = altfmt +human.pressurefmt = pressurefmt +human.distancefmt = distancefmt +human.speedfmt = speedfmt + +function human.convert(value, utype, tounit) + local converttbl = conversiontbl[utype] + + if converttbl == nil then + return nil + end + + local totbl = converttbl[tounit] + + if totbl == nil then + return nil + end + + return value * totbl.ratio, totbl.symbol +end + --- enemy air superiority as defined by the US-DOD is -- 'incapability', 'denial', 'parity', 'superiority', -- 'supremacy' - this is simply represented by a number @@ -93,6 +228,101 @@ function human.relationship(side1, side2) end end +function human.degrade_position(position, precision) + local lat, long = coord.LOtoLL(position) + lat, long = degradeLL(lat, long, precision) + return coord.LLtoLO(lat, long, 0) +end + +function human.LLtostring(lat, long, precision, fmt) + local northing = "N" + local easting = "E" + local degsym = '°' + + if lat < 0 then + northing = "S" + end + + if long < 0 then + easting = "W" + end + + lat, long = degradeLL(lat, long, precision) + lat = math.abs(lat) + long = math.abs(long) + + local fmtstr = getLLformatstr(precision, fmt) + + if fmt == posfmt.DD then + return string.format(fmtstr..degsym, lat)..northing.. + " ".. + string.format(fmtstr..degsym, long)..easting + end + + -- we give the minutes and seconds a little push in case the division + -- from the truncation with this multiplication gives us a value ending + -- in .99999... + local tolerance = 1e-8 + + local latdeg = math.floor(lat) + local latmind = (lat - latdeg)*60 + tolerance + local longdeg = math.floor(long) + local longmind = (long - longdeg)*60 + tolerance + + if fmt == posfmt.DDM then + return string.format("%02d"..degsym..fmtstr.."'", latdeg, latmind).. + northing.. + " ".. + string.format("%03d"..degsym..fmtstr.."'", longdeg, longmind).. + easting + end + + local latmin = math.floor(latmind) + local latsecd = (latmind - latmin)*60 + tolerance + local longmin = math.floor(longmind) + local longsecd = (longmind - longmin)*60 + tolerance + + return string.format("%02d"..degsym.."%02d'"..fmtstr.."\"", + latdeg, latmin, latsecd).. + northing.. + " ".. + string.format("%03d"..degsym.."%02d'"..fmtstr.."\"", + longdeg, longmin, longsecd).. + easting +end + +function human.MGRStostring(mgrs, precision) + local str = mgrs.UTMZone .. " " .. mgrs.MGRSDigraph + + if precision == 0 then + return str + end + + local divisor = 10^(5-precision) + local fmtstr = "%0"..precision.."d" + + if precision == 0 then + return str + end + + return str.." "..string.format(fmtstr, (mgrs.Easting/divisor)).. + " "..string.format(fmtstr, (mgrs.Northing/divisor)) +end + +function human.fmtposition(position, precision, fmt) + precision = math.floor(precision) + assert(precision >= 0 and precision <= 5, + "value error: precision range [0,5]") + local lat, long = coord.LOtoLL(position) + + if fmt == posfmt.MGRS then + return human.MGRStostring(coord.LLtoMGRS(lat, long), + precision) + end + + return human.LLtostring(lat, long, precision, fmt) +end + -- TODO: this needs to be refined. It needs to be able to handle -- different character types. Actually it really only makes sense -- to support Agents, the trouble is things like factories and @@ -119,7 +349,7 @@ function human.mission_detail_facts(msn, filter, gridfmt) local intel = fact.position.confidence * dctutils.INTELMAX local line = string.format("%d. %s - %s (%s)\n", k, - dctutils.fmtposition(fact.position.value, + human.fmtposition(fact.position.value, intel, gridfmt), tostring(fact.displayname), health) @@ -163,8 +393,8 @@ end function human.mission_location(player) local msn = player:getMission() - return dctutils.fmtposition(msn:getDescKey("location"), 3, - player:getDescKey("gridfmt")) + return human.fmtposition(msn:getDescKey("location"), 3, + player:getDescKey("gridfmt")) end --- Print target details. Targets are found by iterating over the diff --git a/tests/test-utils.lua b/tests/test-utils.lua index 32b692df..3570c7ba 100755 --- a/tests/test-utils.lua +++ b/tests/test-utils.lua @@ -3,13 +3,14 @@ require("os") require("dcttestlibs") require("dct") -local utils = require("dct.libs.utils") local json = require("libs.json") +local utils = require("dct.libs.utils") +local uihuman = require("dct.ui.human") local formats = { - ["DD"] = utils.posfmt.DD, - ["DDM"] = utils.posfmt.DDM, - ["DMS"] = utils.posfmt.DMS, + ["DD"] = uihuman.posfmt.DD, + ["DDM"] = uihuman.posfmt.DDM, + ["DMS"] = uihuman.posfmt.DMS, } local testll = { @@ -96,7 +97,7 @@ local testlo = { ["z"] = -50.35, }, ["precision"] = 3, - ["format"] = utils.posfmt.MGRS, + ["format"] = uihuman.posfmt.MGRS, ["expected"] = "DD GJ 012 567", }, [2] = { @@ -106,7 +107,7 @@ local testlo = { ["z"] = -50.35, }, ["precision"] = 5, - ["format"] = utils.posfmt.DMS, + ["format"] = uihuman.posfmt.DMS, ["expected"] = "88°07'22.800\"N 063°27'21.600\"W", }, } @@ -157,7 +158,7 @@ local testcentroid = { local function main() for _, coord in ipairs(testll) do for fmtkey, fmt in pairs(formats) do - local str = utils.LLtostring(coord.lat, coord.long, + local str = uihuman.LLtostring(coord.lat, coord.long, coord.precision, fmt) assert(str == coord[fmtkey], string.format( "utils.LLtostring() with %s (precision %d): ".. @@ -166,13 +167,13 @@ local function main() end end for _, v in ipairs(testmgrs) do - local str = utils.MGRStostring(v.mgrs, v.precision) + local str = uihuman.MGRStostring(v.mgrs, v.precision) assert(str == v.expected, "utils.MGRStostring() unexpected value; got: '"..str.. "'; expected: '"..v.expected.."'") end for _, v in ipairs(testlo) do - local str = utils.fmtposition(v.position, v.precision, v.format) + local str = uihuman.fmtposition(v.position, v.precision, v.format) assert(str == v.expected, "utils.fmtposition unexpected value; got: '"..str.. "'; expected: '"..v.expected.."'")