parking
This commit is contained in:
Frank
2019-11-19 23:08:02 +01:00
parent 8c91c753ee
commit f79ec32c2b
2 changed files with 273 additions and 46 deletions
+26 -37
View File
@@ -419,24 +419,18 @@ function FLIGHTCONTROL:_CheckQueues()
local nparking=#self.Qparking local nparking=#self.Qparking
-- Get next flight in line: either holding or parking. -- Get next flight in line: either holding or parking.
local flight, isholding=self:_GetNextFlight() local flight, isholding, parking=self:_GetNextFlight()
-- Check if somebody wants something. -- Check if somebody wants something.
if flight and ntakeoff==0 and nlanding==0 then if flight and ntakeoff==0 and nlanding==0 then
if isholding then if isholding and parking then
-------------------- --------------------
-- Holding flight -- -- Holding flight --
-------------------- --------------------
-- Get free parking spots.
local n,parking=self:_GetFreeParkingSpots()
-- Get number of alive elements.
local Ne=flight:GetNelements()
-- Message. -- Message.
local text=string.format("Flight %s, you are cleared to land.", flight.groupname) local text=string.format("Flight %s, you are cleared to land.", flight.groupname)
MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll()
@@ -484,65 +478,60 @@ end
-- @param #FLIGHTCONTROL self -- @param #FLIGHTCONTROL self
-- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready. -- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready.
-- @return #boolean If true, flight is holding and waiting for landing, if false, flight is parking and waiting for takeoff. -- @return #boolean If true, flight is holding and waiting for landing, if false, flight is parking and waiting for takeoff.
-- @return #table Parking data for holding flights or nil.
function FLIGHTCONTROL:_GetNextFlight() function FLIGHTCONTROL:_GetNextFlight()
local flightholding=self:_GetNextFightHolding() local flightholding=self:_GetNextFightHolding()
local flightparking=self:_GetNextFightParking() local flightparking=self:_GetNextFightParking()
-- If no flight is waiting for landing just return the takeoff flight or nil.
-- Free parking spots. if not flightholding then
local nP=self:_GetFreeParkingSpots() return flightparking, false, nil
-- Get number of alive elements of the holding flight.
local nH=0
if flightholding then
nH=flightholding:GetNelements()
end end
--env.info("FF") -- Get number of alive elements of the holding flight.
--self:I({nextholding=flightholding}) local nH=flightholding:GetNelements()
--self:I({nextparking=flightparking})
--self:I(string.format("GetNext: holding=%d - parking=%d", nH, nP)) -- Free parking spots.
local parking=flightholding:GetParking(self.airbase)
-- If no flight is waiting for takeoff return the holding flight or nil. -- If no flight is waiting for takeoff return the holding flight or nil.
if not flightparking then if not flightparking then
if nP>=nH then if parking then
return flightholding, true return flightholding, true, parking
else else
self:E(string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", nP, nH)) self:E(string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", #parking, nH))
return nil, nil return nil, nil, nil
end end
end end
-- If no flight is waiting for landing return the takeoff flight or nil.
if not flightholding then
return flightparking, false
end
-- We got flights waiting for landing and for takeoff. -- We got flights waiting for landing and for takeoff.
if flightholding and flightparking then if flightholding and flightparking then
-- Return holding flight if fuel is low. -- Return holding flight if fuel is low.
if flightholding.fuellow then if flightholding.fuellow then
if nP>=nH then if parking then
-- Enough parking ==> land -- Enough parking ==> land
return flightholding, true return flightholding, true, parking
else else
-- Not enough parking ==> take off -- Not enough parking ==> take off
return flightparking, false return flightparking, false, nil
end end
end end
-- Return the flight which is waiting longer. -- Return the flight which is waiting longer.
if flightholding.Tholding>flightparking.Tparking and nP>=nH then if flightholding.Tholding>flightparking.Tparking and parking then
return flightholding, true return flightholding, true, parking
else else
return flightparking, false return flightparking, false, nil
end end
end end
return nil, nil return nil, nil, nil
end end
@@ -941,7 +930,7 @@ function FLIGHTCONTROL:_UpdateParkingSpots()
parking.Coordinate:RemoveMark(parking.markerid) parking.Coordinate:RemoveMark(parking.markerid)
end end
local text=string.format("ID=%03d, Terminal=%03d, Free=%s, Reserved=%s, reserved4=%s, Dist=%.1f", parking.TerminalID, parking.TerminalType, tostring(parking.Free), tostring(parking.TOAC), parking.reserved, parking.DistToRwy) local text=string.format("ID=%03d, Terminal=%03d, Free=%s, TOAC=%s, reserved=%s", parking.TerminalID, parking.TerminalType, tostring(parking.Free), tostring(parking.TOAC), parking.reserved)
message=message.."\n"..text message=message.."\n"..text
if parking.Free==false or parking.TOAC or parking.reserved~="none" then if parking.Free==false or parking.TOAC or parking.reserved~="none" then
parking.markerid=parking.Coordinate:MarkToAll(text) parking.markerid=parking.Coordinate:MarkToAll(text)
@@ -1459,7 +1448,7 @@ function FLIGHTCONTROL:_LandAI(flight, parking)
spot.reserved=unit.name spot.reserved=unit.name
unit.parking_landing=spot.TerminalID unit.parking_landing=spot.TerminalID
local text=string.format("FF Reserving parking spot %d for unit %s", spot.TerminalID, tostring(unit.name)) local text=string.format("FF Reserving parking spot %d for unit %s", spot.TerminalID, tostring(unit.name))
env.info(text) self:I(self.lid..text)
end end
MESSAGE:New("Respawning group"):ToAll() MESSAGE:New("Respawning group"):ToAll()
+246 -8
View File
@@ -33,6 +33,7 @@
-- @field Core.Set#SET_UNIT detectedunits Set of detected units. -- @field Core.Set#SET_UNIT detectedunits Set of detected units.
-- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. -- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group.
-- @field Wrapper.Airbase#AIRBASE destination The destination base of the flight group. -- @field Wrapper.Airbase#AIRBASE destination The destination base of the flight group.
-- @field #string attribute Generalized attribute.
-- @field #string actype Type name of the aircraft. -- @field #string actype Type name of the aircraft.
-- @field #number speedmax Max speed in km/h. -- @field #number speedmax Max speed in km/h.
-- @field #number range Range of the aircraft in km. -- @field #number range Range of the aircraft in km.
@@ -80,6 +81,7 @@ FLIGHTGROUP = {
detectedunits = {}, detectedunits = {},
homebase = nil, homebase = nil,
destination = nil, destination = nil,
attribute = nil,
actype = nil, actype = nil,
speedmax = nil, speedmax = nil,
range = nil, range = nil,
@@ -123,6 +125,27 @@ FLIGHTGROUP.ElementStatus={
DEAD="dead", DEAD="dead",
} }
--- Generalized attribute. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
-- @type FLIGHTGROUP.Attribute
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
-- @field #string AWACS Airborne Early Warning and Control System.
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
-- @field #string TANKER Airplane which can refuel other aircraft.
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
-- @field #string ATTACKHELO Attack helicopter.
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
FLIGHTGROUP.Attribute = {
TRANSPORTPLANE="TransportPlane",
AWACS="AWACS",
FIGHTER="Fighter",
BOMBER="Bomber",
TANKER="Tanker",
TRANSPORTHELO="TransportHelo",
ATTACKHELO="AttackHelo",
UAV="UAV",
OTHER="Other",
}
--- Flight group element. --- Flight group element.
-- @type FLIGHTGROUP.Element -- @type FLIGHTGROUP.Element
@@ -1079,9 +1102,9 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element)
else else
-- Get parking spot. -- Get parking spot.
local spot, dist=self:GetParkingSpot(Element) local spot=self:GetParkingSpot(Element, 10)
if dist<10 then if spot then
Element.parking=spot Element.parking=spot
@@ -1185,7 +1208,19 @@ end
-- @param #string To To state. -- @param #string To To state.
-- @param #FLIGHTGROUP.Element Element The flight group element. -- @param #FLIGHTGROUP.Element Element The flight group element.
function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) function FLIGHTGROUP:onafterElementDead(From, Event, To, Element)
self:T2(self.sid..string.format("Element dead %s.", Element.name)) self:I(self.sid..string.format("Element dead %s.", Element.name))
-- Clear reserved parking spot.
local spot=self:GetParkingSpot(Element, 10)
if self.flightcontrol and spot then
for _,p in pairs(self.flightcontrol.parking) do
local parking=p --Ops.FlightControl#FLIGHTCONTROL.ParkingSpot
if parking.TerminalID==spot.TerminalID then
parking.reserved="none"
break
end
end
end
-- Set element status. -- Set element status.
self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD) self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD)
@@ -2030,6 +2065,7 @@ function FLIGHTGROUP:AddElementByName(unitname)
element.category=element.unit:GetCategory() element.category=element.unit:GetCategory()
element.categoryname=element.unit:GetCategoryName() element.categoryname=element.unit:GetCategoryName()
element.callsign=element.unit:GetCallsign() element.callsign=element.unit:GetCallsign()
element.size=element.unit:GetObjectSize()
if element.skill=="Client" or element.skill=="Player" then if element.skill=="Client" or element.skill=="Player" then
element.ai=false element.ai=false
@@ -3017,12 +3053,12 @@ function FLIGHTGROUP:GetCoalition()
return self.group:GetCoalition() return self.group:GetCoalition()
end end
--- Returns the coalition side. --- Returns the parking spot of the element.
-- @param #FLIGHTGROUP self -- @param #FLIGHTGROUP self
-- @param #FLIGHTGROUP.Element element Element of the flight group. -- @param #FLIGHTGROUP.Element element Element of the flight group.
-- @return Wrapper.Airbase#AIRBASE.ParkingSpot -- @param #number maxdist Distance threshold in meters. Default 5 m.
-- @return #number Distance to spot in meters. -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold.
function FLIGHTGROUP:GetParkingSpot(element) function FLIGHTGROUP:GetParkingSpot(element, maxdist)
local coord=element.unit:GetCoordinate() local coord=element.unit:GetCoordinate()
@@ -3030,7 +3066,11 @@ function FLIGHTGROUP:GetParkingSpot(element)
local _,_,dist,spot=coord:GetClosestParkingSpot(ab) local _,_,dist,spot=coord:GetClosestParkingSpot(ab)
return spot, dist if dist<=maxdist and not element.unit:InAir() then
return spot
else
return nil
end
end end
--- Get holding time. --- Get holding time.
@@ -3077,6 +3117,204 @@ function FLIGHTGROUP:GetNelements(status)
return n return n
end end
--- Seach unoccupied parking spots at the airbase for all flight elements.
-- @param #FLIGHTGROUP self
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
-- @return #table Table of coordinates and terminal IDs of free parking spots.
function FLIGHTGROUP:GetParking(airbase)
-- Init default
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=true
-- Function calculating the overlap of two (square) objects.
local function _overlap(l1,l2,dist)
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
local safe = (dist > safedist)
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe)))
return safe
end
-- Get airbase category.
local airbasecategory=airbase:GetAirbaseCategory()
-- Get parking spot data table. This contains all free and "non-free" spots.
local parkingdata
if self.flightcontrol then
parkingdata=self.flightcontrol.parking
else
parkingdata=airbase:GetParkingSpotsTable()
end
parkingdata=airbase:GetParkingSpotsTable()
-- List of obstacles.
local obstacles={}
-- Loop over all parking spots and get the currently present obstacles.
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
-- The alternative would be to perform the scan once but with a much larger radius and store all data.
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
-- Scan a radius of 100 meters around the spot.
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
-- Check all units.
for _,_unit in pairs(_units) do
local unit=_unit --Wrapper.Unit#UNIT
local _coord=unit:GetCoordinate()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
end
-- Check all clients.
--[[
for _,_unit in pairs(_units) do
local unit=_unit --Wrapper.Unit#UNIT
local _coord=unit:GetCoordinate()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="client"})
end
]]
-- Check all statics.
for _,static in pairs(_statics) do
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=static:getName()
local _size=self:_GetObjectSize(static)
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
end
-- Check all scenery.
for _,scenery in pairs(_sceneries) do
local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=scenery:getTypeName()
local _size=self:_GetObjectSize(scenery)
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
end
end
-- Parking data for all assets.
local parking={}
-- Get terminal type.
-- TODO: get terminal type
--local terminaltype=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
local terminaltype=AIRBASE.TerminalType.OpenMedOrBig
-- Loop over all units - each one needs a spot.
for i,_element in pairs(self.elements) do
local element=_element --#FLIGHTGROUP.Element
-- Loop over all parking spots.
local gotit=false
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Check correct terminal type for asset. We don't want helos in shelters etc.
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
local _toac=parkingspot.TOAC
local free=true
local problem=nil
-- Safe parking using TO_AC from DCS result.
if verysafe and _toac then
free=false
self:I(self.sid..string.format("Parking spot %d is occupied by other aircraft taking off or landing.", _termid))
end
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle.
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(element.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
free=false
problem=obstacle
problem.dist=dist
break
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this element.
table.insert(parking, parkingspot)
self:I(self.sid..string.format("Parking spot #%d is free for element %s!", _termid, element.name))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=_spot, size=element.size, name=element.name, type="element"})
gotit=true
break
else
-- Debug output for occupied spots.
self:I(self.sid..string.format("Parking spot #%d is occupied or not big enough!", _termid))
--if self.Debug then
-- local coord=problem.coord --Core.Point#COORDINATE
-- local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
-- coord:MarkToAll(string.format(text))
--end
end
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one asset :(
if not gotit then
self:I(self.sid..string.format("WARNING: No free parking spot for element %s", element.name))
return nil
end
end -- loop over asset units
return parking
end
--- Size of the bounding box of a DCS object derived from the DCS descriptor table. If boundinb box is nil, a size of zero is returned.
-- @param #FLIGHTGROUP self
-- @param DCS#Object DCSobject The DCS object for which the size is needed.
-- @return #number Max size of object in meters (length (x) or width (z) components not including height (y)).
-- @return #number Length (x component) of size.
-- @return #number Height (y component) of size.
-- @return #number Width (z component) of size.
function FLIGHTGROUP:_GetObjectSize(DCSobject)
local DCSdesc=DCSobject:getDesc()
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) --length
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) --width
return math.max(x,z), x , y, z
end
return 0,0,0,0
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------