From 4e24a7bf80d191525abd273433e94a703d175758 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 8 Oct 2023 21:49:15 +0200 Subject: [PATCH] Navigation --- Moose Development/Moose/Core/Point.lua | 17 + Moose Development/Moose/Core/Vector.lua | 42 +++ .../Moose/Navigation/FlightPlan.lua | 295 +++++++++++++++++- Moose Development/Moose/Navigation/Point.lua | 102 +++++- .../Moose/Navigation/Procedure.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 8 +- Moose Development/Moose/Ops/OpsGroup.lua | 12 +- Moose Development/Moose/Utilities/Utils.lua | 17 +- Moose Development/Moose/Wrapper/Airbase.lua | 61 +++- 9 files changed, 524 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 97730f0ac..fdb30a528 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -468,6 +468,23 @@ do -- COORDINATE end + --- Returns the coordinate from the latitude and longitude given in degrees, minutes and seconds (DMS). + -- @param #COORDINATE self + -- @param #string Latitude Latitude in DMS as string, e.g. "`42° 24' 14.3"`". Not that the characters `°`, `'` and `"` are important. + -- @param #string Longitude Longitude in DMS as string, e.g. "`42° 24' 14.3"`". Not that the characters `°`, `'` and `"` are important. + -- @param #number Altitude (Optional) Altitude in meters. Default is the land height at the coordinate. + -- @return #COORDINATE + function COORDINATE:NewFromLLDMS(Latitude, Longitude, Altitude) + + local lat=UTILS.LLDMSstringToDD(Latitude) + local lon=UTILS.LLDMSstringToDD(Longitude) + + self=COORDINATE:NewFromLLDD(lat, lon, Altitude) + + return self + end + + --- Returns if the 2 coordinates are at the same 2D position. -- @param #COORDINATE self -- @param #COORDINATE Coordinate diff --git a/Moose Development/Moose/Core/Vector.lua b/Moose Development/Moose/Core/Vector.lua index c12fe2ef6..9a9aeef61 100644 --- a/Moose Development/Moose/Core/Vector.lua +++ b/Moose Development/Moose/Core/Vector.lua @@ -270,6 +270,48 @@ function VECTOR:NewDirectionalVector(a, b) return c end + +--- Creates a new VECTOR instance from given the latitude and longitude in decimal degrees (DD). +-- @param #VECTOR self +-- @param #number Latitude Latitude in decimal degrees. +-- @param #number Longitude Longitude in decimal degrees. +-- @param #number altitude (Optional) Altitude in meters. Default is the land height at the 2D position. +-- @return #VECTOR self +function VECTOR:NewFromLLDD(Latitude, Longitude, Altitude) + + -- Returns a point from latitude and longitude in the vec3 format. + local vec3=coord.LLtoLO(Latitude, Longitude) + + -- Convert vec3 to coordinate object. + self=VECTOR:NewFromVec(vec3) + +-- -- Adjust height +-- if Altitude==nil then +-- self.y=self:GetSurfaceHeight() +-- else +-- self.y=Altitude +-- end + + return self +end + +--- Creates a new VECTOR instance from given latitude and longitude in degrees, minutes and seconds (DMS). +-- **Note** that latitude and longitude are passed as strings and the characters `°`, `'` and `"` are important. +-- @param #VECTOR self +-- @param #string Latitude Latitude in DMS as string, e.g. "`42° 24' 14.3"`". +-- @param #string Longitude Longitude in DMS as string, e.g. "`42° 24' 14.3"`". +-- @param #number Altitude (Optional) Altitude in meters. Default is the land height at the coordinate. +-- @return #VECTOR +function VECTOR:NewFromLLDMS(Latitude, Longitude, Altitude) + + local lat=UTILS.LLDMSstringToDD(Latitude) + local lon=UTILS.LLDMSstringToDD(Longitude) + + self=VECTOR:NewFromLLDD(lat, lon, Altitude) + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Navigation/FlightPlan.lua b/Moose Development/Moose/Navigation/FlightPlan.lua index fb1520337..e8960b27f 100644 --- a/Moose Development/Moose/Navigation/FlightPlan.lua +++ b/Moose Development/Moose/Navigation/FlightPlan.lua @@ -25,11 +25,12 @@ -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity of output. -- @field #table fixes Navigation fixes. +-- @field Core.Pathline#PATHLINE pathline Pathline of the plan. -- @field Wrapper.Airbase#AIRBASE departureAirbase Departure airbase. -- @field Wrapper.Airbase#AIRBASE destinationAirbase Destination airbase. -- @field #number altitudeCruiseMin Minimum cruise altitude in feet MSL. -- @field #number altitudeCruiseMax Maximum cruise altitude in feet MSL. --- @extends Core.Base#BASE +-- @extends Core.Pathline#PATHLINE --- *Life is what happens to us while we are making other plans.* -- Allen Saunders -- @@ -37,12 +38,11 @@ -- -- # The FLIGHTPLAN Concept -- --- A FLIGHTPLAN consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed. +-- This class has a great concept! -- -- # Basic Setup -- --- A new `FLIGHTPLAN` object can be created with the @{#FLIGHTPLAN.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet --- and `FleetName` is the name you want to give the fleet. This must be *unique*! +-- A new `FLIGHTPLAN` object can be created with the @{#FLIGHTPLAN.New}() function. -- -- myFlightplan=FLIGHTPLAN:New("Plan A") -- myFleet:SetPortZone(ZonePort1stFleet) @@ -81,8 +81,10 @@ FLIGHTPLAN.version="0.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: How to connect SID, STAR, ENROUTE, TRANSITION, APPROACH. Typical flightplan SID --> ENROUTE --> STAR --> APPROACH --- TODO: How to handle the FLIGHTGROUP:_LandAtAirBase +-- TODO: Add approach. +-- DONE: How to handle the FLIGHTGROUP:_LandAtAirBase -- TODO: Do we always need a holding pattern? https://www.faa.gov/air_traffic/publications/atpubs/aip_html/part2_enr_section_1.5.html#:~:text=If%20no%20holding%20pattern%20is,than%20that%20desired%20by%20ATC. +-- DOEN: Read from MSFS file. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -94,8 +96,8 @@ FLIGHTPLAN.version="0.0.1" -- @return #FLIGHTPLAN self function FLIGHTPLAN:New(Name) - -- Inherit everything from SCENERY class. - self=BASE:Inherit(self, BASE:New()) -- #FLIGHTPLAN + -- Inherit everything from BASE class. + self=BASE:Inherit(self, PATHLINE:New(Name)) -- #FLIGHTPLAN -- Set alias. self.alias=tostring(Name) @@ -103,6 +105,8 @@ function FLIGHTPLAN:New(Name) -- Set some string id for output to DCS.log file. self.lid=string.format("FLIGHTPLAN %s | ", self.alias) + --self.pathline=PATHLINE:New(Name) + -- Debug info. self:I(self.lid..string.format("Created FLIGHTPLAN!")) @@ -119,30 +123,55 @@ function FLIGHTPLAN:NewFromFlightPlan(FlightPlan) return self end + +--- Create a new FLIGHTPLAN instance from a given file. +-- Currently, the file has to be an MSFS 2020 .pln file as, *e.g.*, exported from [Navigraph](https://navigraph.com/). +-- +-- **Note** that the flight plan does only cover the departure, enroute and arrival portions but **not the approach** part! +-- @param #FLIGHTPLAN self +-- @param #string FileName Full path to file. +-- @return #FLIGHTPLAN self +function FLIGHTPLAN:NewFromFile(FileName) + + if UTILS.FileExists(FileName) then + + self=FLIGHTPLAN._ReadFileMSFS(FileName) + + else + error(string.format("ERROR: File not found! File name=%s", tostring(FileName))) + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Add navigation fix to the flight plan. -- @param #FLIGHTPLAN self --- @param Navigation.NavFix#NAVFIX NavFix The nav fix. +-- @param Navigation.Point#NAVPOINT NavFix The nav fix. -- @return #FLIGHTPLAN self function FLIGHTPLAN:AddNavFix(NavFix) table.insert(self.fixes, NavFix) + + local point=self:AddPointFromVec3(NavFix.vector:GetVec3(true)) + + point.navpoint=NavFix return self end ---- Set depature airbase. +--- Set departure airbase. -- @param #FLIGHTPLAN self -- @param #string AirbaseName Name of the airbase or AIRBASE object. -- @return #FLIGHTPLAN self function FLIGHTPLAN:SetDepartureAirbase(AirbaseName) self.departureAirbase=AIRBASE:FindByName(AirbaseName) - + return self end @@ -171,6 +200,19 @@ function FLIGHTPLAN:SetCruiseAltitude(AltMin, AltMax) return self end +--- Set cruise speed. +-- @param #FLIGHTPLAN self +-- @param #number SpeedMin Minimum speed in knots. +-- @param #number SpeedMax Maximum speed in knots. Default is `SpeedMin`. +-- @return #FLIGHTPLAN self +function FLIGHTPLAN:SetCruiseSpeed(SpeedMin, SpeedMax) + + self.speedCruiseMin=SpeedMin + self.speedCruiseMax=SpeedMax or self.speedCruiseMin + + return self +end + --- Get the name of this flight plan. -- @param #FLIGHTPLAN self @@ -197,11 +239,242 @@ function FLIGHTPLAN:GetCruiseAltitude() return alt end +--- Get cruise speed. This returns a random speed between the set min/max cruise speeds. +-- @param #FLIGHTPLAN self +-- @return #number Cruise speed in knots. +function FLIGHTPLAN:GetCruiseSpeed() + + local speed=250 + + if self.speedCruiseMin and self.speedCruiseMax then + speed=math.random(self.speedCruiseMin, self.speedCruiseMax) + elseif self.speedCruiseMin then + speed=self.speedCruiseMin + elseif self.altitudeCruiseMax then + speed=self.speedCruiseMax + end + + return speed +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Private Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- No private functions yet. +--- Read flight plan from a given MSFS 2020 .plt file. +-- @param #string FileName Name of the file. +-- @return #FLIGHTPLAN The flight plan. +function FLIGHTPLAN._ReadFileMSFS(FileName) + + local function readfile(filename) + + local lines = {} + + -- Open file in read binary mode. + local file=assert(io.open(filename, "rb"), string.format("File not found! File name = %s", tostring(filename))) + + for line in file:lines() do + lines[#lines+1] = line + end + + -- Close file. + file:close() + + -- Return data + return lines + end + + + --- This function returns an XML element, i.e. the string between <...> and . + local function getXMLelement(line) + local element=string.match(line, ">(.+)<") + return element + end + + --- This function returns Latitude and Longitude + local function getLatLong(line) + local latlong=getXMLelement(line) + -- The format is "N41° 38' 20.00",E41° 33' 19.00",+000000.00" so we still need to process that. + local lat,long=string.match(latlong, "(.+),(.+),") + return lat,long + end + + -- Read data from file. + local data=readfile(FileName) + + local flightplan={} + local waypoints={} + local wp=nil + + local gotwaypoint=false + for i,line in pairs(data) do + + + --print(line) + + -- Title + if string.find(line, "") then + flightplan.title=getXMLelement(line) + end + + -- Departure ICAO + if string.find(line, "<DepartureID>") then + flightplan.departureICAO=getXMLelement(line) + end + + -- Destination ICAO + if string.find(line, "<DestinationID>") then + flightplan.destinationICAO=getXMLelement(line) + end + + -- FPType + if string.find(line, "<FPType>") then + flightplan.plantype=getXMLelement(line) + end + + -- Route type + if string.find(line, "<RouteType>") then + flightplan.routetype=getXMLelement(line) + end + + -- Cruise alt in feet + if string.find(line, "<CruisingAlt>") then + flightplan.altCruise=getXMLelement(line) + end + + -- Departure LLA + if string.find(line, "<DepartureLLA>") then + local lat,long=getLatLong(line) + end + + -- Destination LLA + if string.find(line, "<DestinationLLA>") then + local lat,long=getLatLong(line) + end + + -- Departure Name + if string.find(line, "<DepartureName>") then + local DepartureName=getXMLelement(line) + end + + -- DestinationName + if string.find(line, "<DestinationName>") then + local DestinationName=getXMLelement(line) + end + + --- + -- Waypoint stuff + --- + + -- New waypoint starts. + if string.find(line, "ATCWaypoint id") then + + --Get string inside quotes " and ". + local wpid=string.match(line, [["(.+)"]]) + + -- Create a new wp table. + wp={} + + -- Set waypoint name. + wp.name=wpid + end + + -- Waypoint info ends. + if string.find(line, "</ATCWaypoint>") then + -- This is the end of the waypoint. + + -- Add info to waypoints table. + table.insert(waypoints, wp) + + -- Set waypoint to nil. We create an empty table if the next wp starts. + wp=nil + end + + -- Waypoint type (Airport, Intersection, NDB, VORTAC) + if string.find(line, "<ATCWaypointType>") then + local wptype=getXMLelement(line) + wp.type=wptype + end + + -- Waypoint position. + if string.find(line, "<WorldPosition>") then + wp.lat, wp.long=getLatLong(line) + end + + -- Runway should exist for initial and final WP if it is an airport. + if string.find(line, "RunwayNumberFP") then + wp.runway=getXMLelement(line) + end + + -- Runway designator: LEFT, RIGHT, CENTER + if string.find(line, "RunwayDesignatorFP") then + wp.runwayDesignator=getXMLelement(line) + end + + -- Segment is Departure + if string.find(line, "<DepartureFP>") then + wp.segment="Departure" + end + + -- Segment is Arrival + if string.find(line, "<ArrivalFP>") then + wp.segment="Arrival" + end + + -- Segment is Enroute + if string.find(line, "<ATCAirway>") then + wp.segment="Enroute" + end + + -- Approach type: VORDME, LOCALIZER + if string.find(line, "ApproachTypeFP") then + flightplan.approachtype=getXMLelement(line) + end + + -- Approach type suffic: Z + if string.find(line, "SuffixFP") then + local SuffixFP=getXMLelement(line) + end + + end + + for key, value in pairs(flightplan) do + env.info(string.format("Flightplan %s=%s", key, tostring(value))) + end + + env.info(string.format("Number of waypoints=%d", #waypoints)) + for i,wp in pairs(waypoints) do + env.info(string.format("Waypoint name=%s type=%s segment=%s runway=%s lat=%s long=%s", wp.name, wp.type, tostring(wp.segment), tostring(wp.runway)..tostring(wp.runwayDesignator or ""), wp.lat, wp.long)) + end + + -- Create a new flightplan. + local fp=FLIGHTPLAN:New(flightplan.title) + + -- Set cruise altitude. + fp:SetCruiseAltitude(flightplan.altCruise) + + -- Set departure and destination airports. + fp:SetDepartureAirbase(flightplan.departureICAO) + fp:SetDestinationAirbase(flightplan.destinationICAO) + + --TODO: Remove first and last waypoint if they are identical to the departure/destination airport! + + for i,wp in pairs(waypoints) do + + -- Create a navpoint. + local navpoint=NAVPOINT:NewFromLLDMS(wp.name, wp.type, wp.lat, wp.long) + + navpoint:SetAltMin(flightplan.altCruise) + + -- Add point to flightplan. + -- TODO: section departure, enroute, arrival. + fp:AddNavFix(navpoint) + end + + + + return fp +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Navigation/Point.lua b/Moose Development/Moose/Navigation/Point.lua index 2ccfbf2dc..5bf6f5d4c 100644 --- a/Moose Development/Moose/Navigation/Point.lua +++ b/Moose Development/Moose/Navigation/Point.lua @@ -97,7 +97,7 @@ function NAVAID:New(ZoneName, SceneryName, Type) self.zone=ZONE:FindByName(ZoneName) - self=self.zone:GetCoordinate() + self.coordinate=self.zone:GetCoordinate() if SceneryName then self.scenery=SCENERY:FindByNameInZone(SceneryName, ZoneName) @@ -364,6 +364,7 @@ end -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity of output. -- @field #string name Name of the point. +-- @field #string typePoint Type of the point, *e.g. "Intersection", "VOR", "Airport". -- @field Core.Vector#VECTOR vector Position vector of the fix. -- @field Wrapper.Marker#MARKER marker Marker on F10 map. -- @field #number altMin Minimum altitude in meters. @@ -377,13 +378,16 @@ end -- -- === -- --- # The NAVFIX Concept +-- # The NAVPOINT Concept -- --- The NAVFIX class has a great concept! +-- The NAVPOINT class has a great concept! +-- +-- A NAVPOINT describes a geo position and can, *e.g.*, be part of a FLIGHTPLAN. It has a unique name and is of a certain type, *e.g.* "Intersection", "VOR", "Airbase" etc. +-- It can also have further properties as min/max altitudes and speeds that aircraft need to obey when they pass the point. -- -- # Basic Setup -- --- A new `NAVFIX` object can be created with the @{#NAVFIX.New}() function. +-- A new `NAVPOINT` object can be created with the @{#NAVPOINT.New}() function. -- -- myNavPoint=NAVPOINT:New() -- myTemplate:SetXYZ(X, Y, Z) @@ -396,6 +400,21 @@ NAVPOINT = { verbose = 0, } +--- Type of point. +-- @type NAVPOINT.Type +-- @field #string VOR VOR +-- @field #string NDB NDB +NAVPOINT.Type={ + POINT="Point", + INTERSECTION="Intersection", + AIRPORT="Airport", + VOR="VOR", + DME="DME", + VORDME="VOR/DME", + LOC="Localizer", + NDB="NDB", +} + --- NAVPOINT class version. -- @field #string version NAVPOINT.version="0.0.1" @@ -412,10 +431,11 @@ NAVPOINT.version="0.0.1" --- Create a new NAVPOINT class instance from a given VECTOR. -- @param #NAVPOINT self --- @param #string Name Name of the fix. Should be unique! +-- @param #string Name Name of the point. Should be unique! +-- @param #string Type Type of the point. Default `NAVPOINT.Type.POINT`. -- @param Core.Vector#VECTOR Vector Position vector of the navpoint. -- @return #NAVPOINT self -function NAVPOINT:NewFromVector(Name, Vector) +function NAVPOINT:NewFromVector(Name, Type, Vector) -- Inherit everything from BASE class. self=BASE:Inherit(self, BASE:New()) -- #NAVFIX @@ -426,6 +446,8 @@ function NAVPOINT:NewFromVector(Name, Vector) -- Name of point. self.name=Name + self.typePoint=Type or NAVPOINT.Type.POINT + -- Marker on F10. self.marker=MARKER:New(Vector:GetCoordinate(true), self:_GetMarkerText()) @@ -439,15 +461,59 @@ end --- Create a new NAVPOINT class instance from a given COORDINATE. -- @param #NAVPOINT self -- @param #string Name Name of the fix. Should be unique! +-- @param #string Type Type of the point. Default `NAVPOINT.Type.POINT`. -- @param Core.Point#COORDINATE Coordinate Coordinate of the point. -- @return #NAVPOINT self -function NAVPOINT:NewFromCoordinate(Name, Coordinate) +function NAVPOINT:NewFromCoordinate(Name, Type, Coordinate) + + -- Create a VECTOR from the coordinate. local Vector=VECTOR:NewFromVec(Coordinate) - self=NAVPOINT:NewFromVector(Name, Vector) + + -- Create NAVPOINT. + self=NAVPOINT:NewFromVector(Name, Type, Vector) + return self end ---- Create a new NAVPOINT class instance from a given NAVPOINT. + +--- Create a new NAVPOINT instance from given latitude and longitude in degrees, minutes and seconds (DMS). +-- @param #NAVPOINT self +-- @param #string Name Name of the fix. Should be unique! +-- @param #string Type Type of the point. Default `NAVPOINT.Type.POINT`. +-- @param #string Latitude Latitude in DMS as string. +-- @param #string Longitude Longitude in DMS as string. +-- @return #NAVPOINT self +function NAVPOINT:NewFromLLDMS(Name, Type, Latitude, Longitude) + + -- Create a VECTOR from the coordinate. + local Vector=VECTOR:NewFromLLDMS(Latitude, Longitude) + + -- Create NAVPOINT. + self=NAVPOINT:NewFromVector(Name, Type, Vector) + + return self +end + +--- Create a new NAVPOINT instance from given latitude and longitude in decimal degrees (DD). +-- @param #NAVPOINT self +-- @param #string Name Name of the fix. Should be unique! +-- @param #string Type Type of the point. Default `NAVPOINT.Type.POINT`. +-- @param #number Latitude Latitude in DD. +-- @param #number Longitude Longitude in DD. +-- @return #NAVPOINT self +function NAVPOINT:NewFromLLDD(Name, Type, Latitude, Longitude) + + -- Create a VECTOR from the coordinate. + local Vector=VECTOR:NewFromLLDD(Latitude, Longitude) + + -- Create NAVPOINT. + self=NAVPOINT:NewFromVector(Name, Type, Vector) + + return self +end + + +--- Create a new NAVPOINT class instance relative to a given other NAVPOINT. -- You have to specify the distance and bearing from the new point to the given point. *E.g.*, for a distance of 5 NM and a bearing of 090° (West), the -- new nav point is created 5 NM East of the given nav point. The reason is that this corresponts to convention used in most maps. -- You can, however, use the `Reciprocal` switch to create the new point in the direction you specify. @@ -517,7 +583,19 @@ end -- @return #NAVPOINT self function NAVPOINT:SetSpeedMin(Speed) - self.speedMin=Altitude + self.speedMin=Speed + + return self +end + + +--- Set maximum speed. +-- @param #NAVPOINT self +-- @param #number Speed Max speed in knots. +-- @return #NAVPOINT self +function NAVPOINT:SetSpeedMax(Speed) + + self.speedMax=Speed return self end @@ -541,13 +619,13 @@ function NAVPOINT:SetFlyOver(FlyOver) end ---- Get the altitude in feet MSL. +--- Get the altitude in feet MSL. If min and max altitudes are set, it will return a random altitude between min and max. -- @param #NAVPOINT self -- @return #number Altitude in feet MSL. Can be `nil`, if neither min nor max altitudes have beeen set. function NAVPOINT:GetAltitude() local alt=nil - if self.altMin and self.altMax then + if self.altMin and self.altMax and self.altMin~=self.altMax then alt=math.random(self.altMin, self.altMax) elseif self.altMin then alt=self.altMin diff --git a/Moose Development/Moose/Navigation/Procedure.lua b/Moose Development/Moose/Navigation/Procedure.lua index 3038e4214..2a0cca784 100644 --- a/Moose Development/Moose/Navigation/Procedure.lua +++ b/Moose Development/Moose/Navigation/Procedure.lua @@ -1,4 +1,4 @@ ---- **NAVIGATION** - Prodedures for Departure (SID), Arrival (STAR) and Approach. +--- **NAVIGATION** - Prodedures for Departure (*e.g.* SID), Enroute, Arrival (*e.g.* STAR) and Approach. -- -- **Main Features:** -- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 38262026c..5bb3ea9da 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -3697,13 +3697,17 @@ end -- Flightplan Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check flightplans. +--- Set flightplan and add waypoints. -- @param #FLIGHTGROUP self -- @param Navigation.FlightPlan#FLIGHTPLAN FlightPlan Flight plan. -- @return #FLIGHTGROUP self function FLIGHTGROUP:_SetFlightPlan(FlightPlan) + self:I(self.lid..string.format("Setting flightplan %s", FlightPlan.alias)) + self.flightplan=FlightPlan + + self:SetDestinationbase(self.flightplan.destinationAirbase) for i,_fix in pairs(FlightPlan.fixes) do local fix=_fix --Navigation.NavFix#NAVFIX @@ -3714,7 +3718,7 @@ function FLIGHTGROUP:_SetFlightPlan(FlightPlan) local altitude=(fix.altMin or fix.altMax)~=nil and fix:GetAltitude() or FlightPlan:GetCruiseAltitude() - local wp=self:AddWaypoint(fix.coordinate, Speed, AfterWaypointWithID, altitude or 10000, false) + local wp=self:AddWaypoint(fix.vector, Speed, AfterWaypointWithID, altitude or 10000, false) wp.flightplan=FlightPlan end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 122b7cd8e..135571e8d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -13493,20 +13493,24 @@ end -- @return Core.Point#COORDINATE The coordinate of the object. function OPSGROUP:_CoordinateFromObject(Object) + env.info("FF coordfrom object") if Object then - if Object:IsInstanceOf("COORDINATE") then + if VECTOR._IsVector(Object) then + env.info("FF VECTOR") + return Object:GetCoordinate() + elseif Object:IsInstanceOf("COORDINATE") then return Object else if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then - self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") + self:E(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") local coord=Object:GetCoordinate() return coord else - self:T(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") + self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") end end else - self:T(self.lid.."ERROR: Object passed is nil!") + self:E(self.lid.."ERROR: Object passed is nil!") end return nil diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b7461eb37..76a51bc04 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -721,7 +721,22 @@ end -- @return #number Latitude or Longitude in decimal degrees. UTILS.LLDMSToDD = function(Degrees, Minutes, Seconds) - local dd=(Degrees or 0) + (Minutes or 0)/60 + (Seconds or 0)/3600 + local dd=tonumber(Degrees or 0) + tonumber(Minutes or 0)/60 + tonumber(Seconds or 0)/3600 + + return dd +end + +--- Convert latitude or longitude from degrees, minutes, seconds (DMS) given in text form to decimal degrees (DD). +-- @param #string LatOrLongString Latitude or longitude passed as ttring in format `DD°MM'SS.SS"`. +-- @return #number Latitude or Longitude in decimal degrees. +UTILS.LLDMSstringToDD = function(LatOrLongString) + + local hem=string.match(LatOrLongString, "(%a)") + local Degrees=string.match(LatOrLongString, "(%d+)°") + local Minutes=string.match(LatOrLongString, "(%d+)'") + local Seconds=string.match(LatOrLongString, "([%d\.]+)\"") + + local dd=UTILS.LLDMSToDD(Degrees, Minutes, Seconds) return dd end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 7510a5be1..4cdf88092 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -753,6 +753,32 @@ AIRBASE.SpotStatus = { RESERVED="Reserved", } +--- ICAO Codes. +-- @type AIRBASE.ICAO +AIRBASE.ICAO = { + UGTB=AIRBASE.Caucasus.Tbilisi_Lochini, + UGKO=AIRBASE.Caucasus.Kutaisi, + UG5X=AIRBASE.Caucasus.Kobuleti, + UG24=AIRBASE.Caucasus.Soganlug, + UG27=AIRBASE.Caucasus.Vaziani, + UGSS=AIRBASE.Caucasus.Sukhumi_Babushara, + UG23=AIRBASE.Caucasus.Gudauta, + URSS=AIRBASE.Caucasus.Sochi_Adler, + URMO=AIRBASE.Caucasus.Beslan, + URMN=AIRBASE.Caucasus.Nalchik, + XRMF=AIRBASE.Caucasus.Mozdok, + URMM=AIRBASE.Caucasus.Mineralnye_Vody, + URKH=AIRBASE.Caucasus.Maykop_Khanskaya, + URKK=AIRBASE.Caucasus.Krasnodar_Pashkovsky, + URKL=AIRBASE.Caucasus.Krasnodar_Center, + URKG=AIRBASE.Caucasus.Gelendzhik, + URKN=AIRBASE.Caucasus.Novorossiysk, + URKW=AIRBASE.Caucasus.Krymsk, + URKA=AIRBASE.Caucasus.Anapa_Vityazevo, +} + + + --- Runway data. -- @type AIRBASE.Runway -- @field #string name Runway name. @@ -875,14 +901,47 @@ end --- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. -- @param #AIRBASE self --- @param #string AirbaseName The Airbase Name. +-- @param #string AirbaseName The name of the airbase. Can also be the ICAO code or the airbase ID. -- @return #AIRBASE self function AIRBASE:FindByName( AirbaseName ) local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) + + if not AirbaseFound then + AirbaseFound=self:FindByICAO(AirbaseName) + end + + if not AirbaseFound then + AirbaseFound=self:FindByID(AirbaseName) + end + return AirbaseFound end + +--- Find an AIRBASE in the _DATABASE using the [International Civil Aviation Organization](https://en.wikipedia.org/wiki/ICAO_airport_code) (ICAO) airport code. +-- The code consists of four characters. Typically, the first one or two letters of the ICAO code indicate the country and the remaining letters identify the airport. +-- +-- **NOTE** that the ICAO code cannot be retrieved via the DCS API and has to be hard coded into the MOOSE code. Therefore, it is a rare occasion where +-- @param #AIRBASE self +-- @param #string AirbaseICAO The Airbase ICAO code. +-- @return #AIRBASE self +function AIRBASE:FindByICAO( AirbaseICAO ) + + if AirbaseICAO then + + local name=AIRBASE.ICAO[AirbaseICAO] + + env.info(string.format("FF ICAO=%s, Name=%s", tostring(AirbaseICAO), tostring(name))) + + local Airbase=self:FindByName(name) + return Airbase + + end + + return nil +end + --- Find a AIRBASE in the _DATABASE by its ID. -- @param #AIRBASE self -- @param #number id Airbase ID.