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
|
-- @module Functional.Stratego
|
||||||
-- @image Functional.Stratego.png
|
-- @image Functional.Stratego.png
|
||||||
-- Last Update April 2024
|
-- Last Update May 2024
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -181,7 +181,7 @@ STRATEGO = {
|
|||||||
debug = false,
|
debug = false,
|
||||||
drawzone = false,
|
drawzone = false,
|
||||||
markzone = false,
|
markzone = false,
|
||||||
version = "0.2.8",
|
version = "0.2.9",
|
||||||
portweight = 3,
|
portweight = 3,
|
||||||
POIweight = 1,
|
POIweight = 1,
|
||||||
maxrunways = 3,
|
maxrunways = 3,
|
||||||
@ -212,9 +212,10 @@ STRATEGO = {
|
|||||||
-- @field #number coalition
|
-- @field #number coalition
|
||||||
-- @field #boolean port
|
-- @field #boolean port
|
||||||
-- @field Core.Zone#ZONE_RADIUS zone,
|
-- @field Core.Zone#ZONE_RADIUS zone,
|
||||||
-- @field Core.Point#COORDINATRE coord
|
-- @field Core.Point#COORDINATE coord
|
||||||
-- @field #string type
|
-- @field #string type
|
||||||
-- @field Ops.OpsZone#OPSZONE opszone
|
-- @field Ops.OpsZone#OPSZONE opszone
|
||||||
|
-- @field #number connections
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @type STRATEGO.DistData
|
-- @type STRATEGO.DistData
|
||||||
@ -490,6 +491,7 @@ function STRATEGO:AnalyseBases()
|
|||||||
coord = coord,
|
coord = coord,
|
||||||
type = abtype,
|
type = abtype,
|
||||||
opszone = opszone,
|
opszone = opszone,
|
||||||
|
connections = 0,
|
||||||
}
|
}
|
||||||
airbasetable[abname] = tbl
|
airbasetable[abname] = tbl
|
||||||
nonconnectedab[abname] = true
|
nonconnectedab[abname] = true
|
||||||
@ -576,10 +578,12 @@ function STRATEGO:AnalysePOIs(Set,Weight,Key)
|
|||||||
coord = coord,
|
coord = coord,
|
||||||
type = Key,
|
type = Key,
|
||||||
opszone = opszone,
|
opszone = opszone,
|
||||||
|
connections = 0,
|
||||||
}
|
}
|
||||||
airbasetable[zone:GetName()] = tbl
|
airbasetable[zname] = tbl
|
||||||
nonconnectedab[zone:GetName()] = true
|
nonconnectedab[zname] = true
|
||||||
local name = string.gsub(zname,"[%p%s]",".")
|
local name = string.gsub(zname,"[%p%s]",".")
|
||||||
|
--self:I({name=name,zone=zname})
|
||||||
easynames[name]=zname
|
easynames[name]=zname
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
@ -598,6 +602,30 @@ function STRATEGO:GetToFrom(StartPoint,EndPoint)
|
|||||||
return fromto, tofrom
|
return fromto, tofrom
|
||||||
end
|
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!
|
--- [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 #STRATEGO self
|
||||||
-- @param #string Startpoint Starting Point, e.g. AIRBASE.Syria.Hatay
|
-- @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
|
local factor = self.airbasetable[Startpoint].baseweight*self.routefactor
|
||||||
self.airbasetable[Startpoint].weight = self.airbasetable[Startpoint].weight+factor
|
self.airbasetable[Startpoint].weight = self.airbasetable[Startpoint].weight+factor
|
||||||
self.airbasetable[Endpoint].weight = self.airbasetable[Endpoint].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
|
if self.debug or Draw then
|
||||||
startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist))
|
startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist))
|
||||||
end
|
end
|
||||||
@ -648,7 +678,7 @@ function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype)
|
|||||||
for _,_data in pairs(self.airbasetable) do
|
for _,_data in pairs(self.airbasetable) do
|
||||||
local fromto,tofrom = self:GetToFrom(startpoint,_data.name)
|
local fromto,tofrom = self:GetToFrom(startpoint,_data.name)
|
||||||
if _data.name == startpoint then
|
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
|
elseif _data.baseweight == tgtrwys and not (self.routexists[fromto] or self.routexists[tofrom]) then
|
||||||
local tgtc = _data.coord
|
local tgtc = _data.coord
|
||||||
local dist = UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000
|
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.nonconnectedab[startpoint] = false
|
||||||
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+factor
|
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+factor
|
||||||
self.airbasetable[_data.name].weight = self.airbasetable[_data.name].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
|
if self.debug then
|
||||||
startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist))
|
startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist))
|
||||||
end
|
end
|
||||||
@ -716,6 +748,8 @@ function STRATEGO:AnalyseUnconnected(Color)
|
|||||||
end
|
end
|
||||||
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+1
|
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+1
|
||||||
self.airbasetable[closest].weight = self.airbasetable[closest].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 = {
|
local data = {
|
||||||
start = startpoint,
|
start = startpoint,
|
||||||
target = closest,
|
target = closest,
|
||||||
@ -732,6 +766,38 @@ function STRATEGO:AnalyseUnconnected(Color)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- [USER] Get a list of the nodes with the highest weight.
|
||||||
-- @param #STRATEGO self
|
-- @param #STRATEGO self
|
||||||
-- @param #number Coalition (Optional) Find for this coalition only. E.g. coalition.side.BLUE.
|
-- @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")
|
self:T(self.lid.."FindNeighborNodes")
|
||||||
local neighbors = {}
|
local neighbors = {}
|
||||||
local name = string.gsub(Name,"[%p%s]",".")
|
local name = string.gsub(Name,"[%p%s]",".")
|
||||||
|
--self:I({Name=Name,name=name})
|
||||||
local shortestdist = 1000*1000
|
local shortestdist = 1000*1000
|
||||||
local nearest = nil
|
local nearest = nil
|
||||||
for _route,_data in pairs(self.disttable) do
|
for _route,_data in pairs(self.disttable) do
|
||||||
if string.find(_route,name,1,true) then
|
if string.find(_route,name,1,true) then
|
||||||
local dist = self.disttable[_route] -- #STRATEGO.DistData
|
local dist = self.disttable[_route] -- #STRATEGO.DistData
|
||||||
|
--self:I({route=_route,name=name})
|
||||||
local tname = string.gsub(_route,name,"")
|
local tname = string.gsub(_route,name,"")
|
||||||
local tname = string.gsub(tname,";","")
|
local tname = string.gsub(tname,";","")
|
||||||
|
--self:I({tname=tname,cname=self.easynames[tname]})
|
||||||
local cname = self.easynames[tname] -- name of target
|
local cname = self.easynames[tname] -- name of target
|
||||||
local encoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
|
if cname then
|
||||||
if Enemies == true then
|
local encoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
|
||||||
if self.airbasetable[cname].coalition == encoa then
|
if Enemies == true then
|
||||||
neighbors[cname] = dist
|
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
|
end
|
||||||
elseif Friends == true then
|
if neighbors[cname] and dist.dist < shortestdist then
|
||||||
if self.airbasetable[cname].coalition ~= encoa then
|
shortestdist = dist.dist
|
||||||
neighbors[cname] = dist
|
nearest = cname
|
||||||
end
|
end
|
||||||
else
|
|
||||||
neighbors[cname] = dist
|
|
||||||
end
|
|
||||||
if neighbors[cname] and dist.dist < shortestdist then
|
|
||||||
shortestdist = dist.dist
|
|
||||||
nearest = cname
|
|
||||||
end
|
end
|
||||||
end
|
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.
|
-- @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 #table Route Table of #string name entries of the route
|
||||||
-- @return #boolean Complete If true, the route was found end-to-end.
|
-- @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)
|
function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
|
||||||
self:T(self.lid.."FindRoute")
|
self:T(self.lid.."FindRoute")
|
||||||
--self:I({Start,End,Hops})
|
--self:I({Start,End,Hops})
|
||||||
--local bases = UTILS.DeepCopy(self.airbasetable)
|
--local bases = UTILS.DeepCopy(self.airbasetable)
|
||||||
local Route = {}
|
local Route = {}
|
||||||
|
local InRoute = {}
|
||||||
local hops = Hops or 4
|
local hops = Hops or 4
|
||||||
local routecomplete = false
|
local routecomplete = false
|
||||||
|
local reverse = false
|
||||||
|
|
||||||
local function Checker(neighbors)
|
local function Checker(neighbors)
|
||||||
for _name,_data in pairs(neighbors) do
|
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 function NextClosest(Start,End)
|
||||||
local ecoord = self.airbasetable[End].coord
|
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 closest = nil
|
||||||
local closedist = 1000*1000
|
local closedist = 1000*1000
|
||||||
for _name,_dist in pairs(nodes) do
|
for _name,_dist in pairs(nodes) do
|
||||||
local kcoord = self.airbasetable[_name].coord
|
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)
|
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
|
closedist = dist
|
||||||
closest = _name
|
closest = _name
|
||||||
end
|
end
|
||||||
@ -1231,6 +1308,7 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
|
|||||||
|
|
||||||
-- One hop
|
-- One hop
|
||||||
Route[#Route+1] = Start
|
Route[#Route+1] = Start
|
||||||
|
InRoute[Start] = true
|
||||||
local nodes = self:FindNeighborNodes(Start)
|
local nodes = self:FindNeighborNodes(Start)
|
||||||
local endpoint = Checker(nodes)
|
local endpoint = Checker(nodes)
|
||||||
|
|
||||||
@ -1240,9 +1318,11 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
|
|||||||
else
|
else
|
||||||
local spoint = Start
|
local spoint = Start
|
||||||
for i=1,hops do
|
for i=1,hops do
|
||||||
|
--self:I("Start="..tostring(spoint))
|
||||||
local Next = NextClosest(spoint,End)
|
local Next = NextClosest(spoint,End)
|
||||||
if Next then
|
if Next then
|
||||||
Route[#Route+1] = Next
|
Route[#Route+1] = Next
|
||||||
|
InRoute[Next] = true
|
||||||
local nodes = self:FindNeighborNodes(Next)
|
local nodes = self:FindNeighborNodes(Next)
|
||||||
local endpoint = Checker(nodes)
|
local endpoint = Checker(nodes)
|
||||||
if endpoint then
|
if endpoint then
|
||||||
@ -1255,8 +1335,54 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
|
|||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
--- [USER] Add budget points.
|
--- [USER] Add budget points.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user