#STRATEGO - Smarter route finding

This commit is contained in:
Applevangelist 2024-05-07 14:33:49 +02:00
parent 522c5e4eaa
commit b1f3cda7f7

View File

@ -15,7 +15,7 @@
--
-- @module Functional.Stratego
-- @image Functional.Stratego.png
-- Last Update April 2024
-- Last Update May 2024
---
@ -181,7 +181,7 @@ STRATEGO = {
debug = false,
drawzone = false,
markzone = false,
version = "0.2.8",
version = "0.2.9",
portweight = 3,
POIweight = 1,
maxrunways = 3,
@ -212,9 +212,10 @@ STRATEGO = {
-- @field #number coalition
-- @field #boolean port
-- @field Core.Zone#ZONE_RADIUS zone,
-- @field Core.Point#COORDINATRE coord
-- @field Core.Point#COORDINATE coord
-- @field #string type
-- @field Ops.OpsZone#OPSZONE opszone
-- @field #number connections
---
-- @type STRATEGO.DistData
@ -490,6 +491,7 @@ function STRATEGO:AnalyseBases()
coord = coord,
type = abtype,
opszone = opszone,
connections = 0,
}
airbasetable[abname] = tbl
nonconnectedab[abname] = true
@ -576,10 +578,12 @@ function STRATEGO:AnalysePOIs(Set,Weight,Key)
coord = coord,
type = Key,
opszone = opszone,
connections = 0,
}
airbasetable[zone:GetName()] = tbl
nonconnectedab[zone:GetName()] = true
airbasetable[zname] = tbl
nonconnectedab[zname] = true
local name = string.gsub(zname,"[%p%s]",".")
--self:I({name=name,zone=zname})
easynames[name]=zname
end
)
@ -598,6 +602,30 @@ function STRATEGO:GetToFrom(StartPoint,EndPoint)
return fromto, tofrom
end
--- [USER] Get available connecting nodes from one start node
-- @param #STRATEGO self
-- @param #string StartPoint The starting name
-- @return #boolean found
-- @return #table Nodes
function STRATEGO:GetRoutesFromNode(StartPoint)
self:T(self.lid.."GetRoutesFromNode")
local pstart = string.gsub(StartPoint,"[%p%s]",".")
local found = false
pstart=pstart..";"
local routes = {}
local listed = {}
for _,_data in pairs(self.routexists) do
if string.find(_data,pstart,1,true) and not listed[_data] then
local target = string.gsub(_data,pstart,"")
local fname = self.easynames[target]
table.insert(routes,fname)
found = true
listed[_data] = true
end
end
return found,routes
end
--- [USER] Manually add a route, for e.g. Island hopping or to connect isolated networks. Use **after** STRATEGO has been started!
-- @param #STRATEGO self
-- @param #string Startpoint Starting Point, e.g. AIRBASE.Syria.Hatay
@ -630,6 +658,8 @@ function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw)
local factor = self.airbasetable[Startpoint].baseweight*self.routefactor
self.airbasetable[Startpoint].weight = self.airbasetable[Startpoint].weight+factor
self.airbasetable[Endpoint].weight = self.airbasetable[Endpoint].weight+factor
self.airbasetable[Endpoint].connections = self.airbasetable[Endpoint].connections + 2
self.airbasetable[Startpoint].connections = self.airbasetable[Startpoint].connections+2
if self.debug or Draw then
startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist))
end
@ -648,7 +678,7 @@ function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype)
for _,_data in pairs(self.airbasetable) do
local fromto,tofrom = self:GetToFrom(startpoint,_data.name)
if _data.name == startpoint then
-- sam as we
-- same as we
elseif _data.baseweight == tgtrwys and not (self.routexists[fromto] or self.routexists[tofrom]) then
local tgtc = _data.coord
local dist = UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000
@ -670,6 +700,8 @@ function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype)
self.nonconnectedab[startpoint] = false
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+factor
self.airbasetable[_data.name].weight = self.airbasetable[_data.name].weight+factor
self.airbasetable[startpoint].connections = self.airbasetable[startpoint].connections + 1
self.airbasetable[_data.name].connections = self.airbasetable[_data.name].connections + 1
if self.debug then
startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist))
end
@ -716,6 +748,8 @@ function STRATEGO:AnalyseUnconnected(Color)
end
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+1
self.airbasetable[closest].weight = self.airbasetable[closest].weight+1
self.airbasetable[startpoint].connections = self.airbasetable[startpoint].connections+2
self.airbasetable[closest].connections = self.airbasetable[closest].connections+2
local data = {
start = startpoint,
target = closest,
@ -732,6 +766,38 @@ function STRATEGO:AnalyseUnconnected(Color)
return self
end
--[[
function STRATEGO:PruneDeadEnds(abtable)
local found = false
local newtable = {}
for name, _data in pairs(abtable) do
local data = _data -- #STRATEGO.Data
if data.connections > 2 then
newtable[name] = data
else
-- dead end
found = true
local neighbors, nearest, distance = self:FindNeighborNodes(name)
--self:I("Pruning "..name)
if nearest then
for _name,_ in pairs(neighbors) do
local abname = self.easynames[_name] or _name
--self:I({easyname=_name,airbasename=abname})
if abtable[abname] then
abtable[abname].connections = abtable[abname].connections -1
end
end
end
if self.debug then
data.coord:CircleToAll(5000,-1,{1,1,1},1,{1,1,1},1,3,true,"Dead End")
end
end
end
abtable = nil
return found,newtable
end
--]]
--- [USER] Get a list of the nodes with the highest weight.
-- @param #STRATEGO self
-- @param #number Coalition (Optional) Find for this coalition only. E.g. coalition.side.BLUE.
@ -1141,14 +1207,18 @@ function STRATEGO:FindNeighborNodes(Name,Enemies,Friends)
self:T(self.lid.."FindNeighborNodes")
local neighbors = {}
local name = string.gsub(Name,"[%p%s]",".")
--self:I({Name=Name,name=name})
local shortestdist = 1000*1000
local nearest = nil
for _route,_data in pairs(self.disttable) do
if string.find(_route,name,1,true) then
local dist = self.disttable[_route] -- #STRATEGO.DistData
--self:I({route=_route,name=name})
local tname = string.gsub(_route,name,"")
local tname = string.gsub(tname,";","")
--self:I({tname=tname,cname=self.easynames[tname]})
local cname = self.easynames[tname] -- name of target
if cname then
local encoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
if Enemies == true then
if self.airbasetable[cname].coalition == encoa then
@ -1167,6 +1237,7 @@ function STRATEGO:FindNeighborNodes(Name,Enemies,Friends)
end
end
end
end
return neighbors, nearest, shortestdist
end
@ -1180,13 +1251,16 @@ end
-- @param #number LineType (Optional) Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 6.
-- @return #table Route Table of #string name entries of the route
-- @return #boolean Complete If true, the route was found end-to-end.
-- @return #boolean Reverse If true, the route was found with a reverse search, the route table will be from sorted from end point to start point.
function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
self:T(self.lid.."FindRoute")
--self:I({Start,End,Hops})
--local bases = UTILS.DeepCopy(self.airbasetable)
local Route = {}
local InRoute = {}
local hops = Hops or 4
local routecomplete = false
local reverse = false
local function Checker(neighbors)
for _name,_data in pairs(neighbors) do
@ -1200,13 +1274,16 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
local function NextClosest(Start,End)
local ecoord = self.airbasetable[End].coord
local nodes = self:FindNeighborNodes(Start)
local nodes,nearest = self:FindNeighborNodes(Start)
--self:I(tostring(nearest))
local closest = nil
local closedist = 1000*1000
for _name,_dist in pairs(nodes) do
local kcoord = self.airbasetable[_name].coord
local nnodes = self.airbasetable[_name].connections > 2 and true or false
if _name == End then nnodes = true end
local dist = math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5)
if dist < closedist then
if (dist < closedist and nnodes and InRoute[_name] ~= true) then
closedist = dist
closest = _name
end
@ -1231,6 +1308,7 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
-- One hop
Route[#Route+1] = Start
InRoute[Start] = true
local nodes = self:FindNeighborNodes(Start)
local endpoint = Checker(nodes)
@ -1240,9 +1318,11 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
else
local spoint = Start
for i=1,hops do
--self:I("Start="..tostring(spoint))
local Next = NextClosest(spoint,End)
if Next then
Route[#Route+1] = Next
InRoute[Next] = true
local nodes = self:FindNeighborNodes(Next)
local endpoint = Checker(nodes)
if endpoint then
@ -1255,8 +1335,54 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
end
end
end
-- optimize route
local function OptimizeRoute(Route)
local foundcut = false
local largestcut = 0
local cut = {}
for i=1,#Route do
--self:I({Start=Route[i]})
local found,nodes = self:GetRoutesFromNode(Route[i])
for _,_name in pairs(nodes or {}) do
for j=i+2,#Route do
if _name == Route[j] then
--self:I({"Shortcut",Route[i],Route[j]})
if j-i > largestcut then
largestcut = j-i
cut = {i=i,j=j}
foundcut = true
end
end
end
end
end
if foundcut then
local newroute = {}
for i=1,#Route do
if i<= cut.i or i>=cut.j then
table.insert(newroute,Route[i])
end
end
return newroute
end
return Route, foundcut
end
if routecomplete == true then
local foundcut = true
while foundcut ~= false do
Route, foundcut = OptimizeRoute(Route)
end
else
-- reverse search
Route, routecomplete = self:FindRoute(End,Start,Hops,Draw,Color,LineType)
reverse = true
end
if (self.debug or Draw) then DrawRoute(Route) end
return Route, routecomplete
return Route, routecomplete, reverse
end
--- [USER] Add budget points.