mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
#STRATEGO - Smarter route finding
This commit is contained in:
parent
522c5e4eaa
commit
b1f3cda7f7
@ -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,29 +1207,34 @@ 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
|
||||
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
|
||||
neighbors[cname] = dist
|
||||
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
|
||||
neighbors[cname] = dist
|
||||
end
|
||||
elseif Friends == true then
|
||||
if self.airbasetable[cname].coalition ~= encoa then
|
||||
neighbors[cname] = dist
|
||||
end
|
||||
else
|
||||
neighbors[cname] = dist
|
||||
end
|
||||
elseif Friends == true then
|
||||
if self.airbasetable[cname].coalition ~= encoa then
|
||||
neighbors[cname] = dist
|
||||
if neighbors[cname] and dist.dist < shortestdist then
|
||||
shortestdist = dist.dist
|
||||
nearest = cname
|
||||
end
|
||||
else
|
||||
neighbors[cname] = dist
|
||||
end
|
||||
if neighbors[cname] and dist.dist < shortestdist then
|
||||
shortestdist = dist.dist
|
||||
nearest = cname
|
||||
end
|
||||
end
|
||||
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 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
|
||||
if (self.debug or Draw) then DrawRoute(Route) end
|
||||
return Route, routecomplete
|
||||
|
||||
-- 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, reverse
|
||||
end
|
||||
|
||||
--- [USER] Add budget points.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user