Canvas Radar: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 35: Line 35:


The Flightgear Next Gen Radar would be able to (not sorted) :  
The Flightgear Next Gen Radar would be able to (not sorted) :  
1) Be a lot faster as actual system (Using Canvas display and some C++ for high consuming function)
1) Be a lot faster as actual system (Using Canvas display and some C++ for high consuming function)
2) Detect Heat/infrared
2) Detect Heat/infrared
3) Manage AESA radar. (Actually it's more complicated to manage non AESA radar -> cause of the sweep. -> No sweep for AESA)
3) Manage AESA radar. (Actually it's more complicated to manage non AESA radar -> cause of the sweep. -> No sweep for AESA)
4) Manage DOPPLER radar DOPPLER (Actually, more complicated to manage "non doppler" radar. The purpose is to erase signature of airplane which are bellow us, and fly at low alt. We could even erase 0 kts aircraft -> The Pougatchev's Cobra should be able to make stall DOPPLER radar)
4) Manage DOPPLER radar DOPPLER (Actually, more complicated to manage "non doppler" radar. The purpose is to erase signature of airplane which are bellow us, and fly at low alt. We could even erase 0 kts aircraft -> The Pougatchev's Cobra should be able to make stall DOPPLER radar)
5) BONUS : Terrain detection. Make an aircraft disapear if it's behind a mountain
5) BONUS : Terrain detection. Make an aircraft disapear if it's behind a mountain
6) Make it working with existing Radio Propagation code.
6) Make it working with existing Radio Propagation code.
7) Manage IFF/Transponder
7) Manage IFF/Transponder
8) Display Radar on Nav Mfd
8) Display Radar on Nav Mfd
9) Allow more than one radar on a plane, like AWACS or Russian aircraft which have a rear radar
9) Allow more than one radar on a plane, like AWACS or Russian aircraft which have a rear radar
10) Manage laser targeting -> with a pod view and clicking (for non AI target like building)
10) Manage laser targeting -> with a pod view and clicking (for non AI target like building)
11) Allow simple re use of the radar on each aircraft military and also civilian
11) Allow simple re use of the radar on each aircraft military and also civilian
12) Long term obj : Put a radar object in "each" missile on the missile.nas script
12) Long term obj : Put a radar object in "each" missile on the missile.nas script
13) More ?
13) More ?



Revision as of 17:17, 6 February 2014

This article is a stub. You can help the wiki by expanding it.
This article describes content/features that may not yet be available in the latest stable version of FlightGear (2020.3).
You may need to install some extra components, use the latest development (Git) version or even rebuild FlightGear from source, possibly from a custom topic branch using special build settings: .

This feature is scheduled for FlightGear (unknown). 10}% completed

If you'd like to learn more about getting your own ideas into FlightGear, check out Implementing new features for FlightGear.

Canvas Radar Framework
MapStructure-TFC-Troubleshooting.png
Started in 02/2014
Description Radar Framework
Maintainer(s) 5H1N0B1, Hooray
Contributor(s) 5H1N0B1 (since 02/2014),
Status Under active development as of 02/2014
Subforum http://forum.flightgear.org/viewforum.php?f=71


Canvas Radar is a MapStructure-based Nasal-space Canvas-Framework for creating Radar displays in FlightGear, as of 02/2014 it is being actively developed by 5H1N0B1 with a focus on the Mirage 2000-5 fighter jet. The Radar implementation is heavily based on xiii's original code and will be further generalized and adapted to be usable in the MVC-centric design of Philosopher's MapStructure framework.

The Radar framework is being closely developed in conjunction with F-JYL's CanvasMFD framework to ensure that aircraft developers can easily adopt the framework in their own aircraft, without having to modify a ton of Nasal code.


Objective

Come up with a generic radar framework for MapStructure that is completely aircraft-agnostic, so that it can be easily adopted, integrated and customized by aircraft developers, but also by people doing ATC related projects, like ATC-FS or ATC-aircraft.

Currently, we're in touch with Adrian to see if his Radio Propagation could be rebased against git/next and exposed via Nasal/CppBind to provide a better option for radar modeling. This is something that we should probably discuss with xiii (developer of the original Nasal code) and vivian (agradar developer) at some point, vivian also expressed interest in updating the agradar to support the Radio Propagation code written by Adrian[1].

Also, i4dnf mentioned that we could benefit from making antenna profiles configurable for different aircraft. Adrian mentioned that he has access to the radar manual of the F16 and that he's familiar with the various operating modes.

The Flightgear Next Gen Radar would be able to (not sorted) :

1) Be a lot faster as actual system (Using Canvas display and some C++ for high consuming function)

2) Detect Heat/infrared

3) Manage AESA radar. (Actually it's more complicated to manage non AESA radar -> cause of the sweep. -> No sweep for AESA)

4) Manage DOPPLER radar DOPPLER (Actually, more complicated to manage "non doppler" radar. The purpose is to erase signature of airplane which are bellow us, and fly at low alt. We could even erase 0 kts aircraft -> The Pougatchev's Cobra should be able to make stall DOPPLER radar)

5) BONUS : Terrain detection. Make an aircraft disapear if it's behind a mountain

6) Make it working with existing Radio Propagation code.

7) Manage IFF/Transponder

8) Display Radar on Nav Mfd

9) Allow more than one radar on a plane, like AWACS or Russian aircraft which have a rear radar

10) Manage laser targeting -> with a pod view and clicking (for non AI target like building)

11) Allow simple re use of the radar on each aircraft military and also civilian

12) Long term obj : Put a radar object in "each" missile on the missile.nas script

13) More ?

Status

Currently, we're playing around with a modified version of the MapStructure TFC (traffic) layer, to use this in a standalone XML dialog and customize it as needed. Once that is working as expected, we will investigate adding xiii's radar modeling code and replace the traffic/TCAS functionality accordingly. Afterwards, we will need to integrate this with the Canvas MFD Framework to ensure that aircraft developers can easily use the framework in their cockpits.

At that point, it would then also make sense to check out the Category:Fighter aircraft and Category:Carrier-based aircraft categories to get in touch with the developers and maintainers of aircraft with RADAR, and ask them for adoption/integration feedback.

Subsequently, it would make sense to look at existing hard-coded displays, like the agradar, groundradar and wxradar to support these use-cases, too. Specifically, ATC-FS and ATC-aircraft could greatly benefit from this effort.

5H1N0B1 also mentioned that a properly-designed component would allow other uses, too - such as for example, equipping Scripted AI Missiles with a real RADAR, or adding radar support to Bombable - likewise, it would be possible to add such radar displays to AWACS aircraft, or even to vessels like the Nimitz.

Radar/ATC Requirements

These are primarily requirements for the Mirage2000 that is currently being revamped by 5H1N0B1[2], these changes could also be used to add ATC support to AWACS aircraft[3].

  • Set up a dialog-based test bed Done Done
  • Prototype a simple ATC/RADAR layer 40}% completed
  • Terrain layer (elevation height maps using the terrain presampler) see: [4] and [5] Not done Not done (by 5H1N0B1)
  • Custom Controllers for:
    • azimuth-based filtering Not done Not done (by 5H1N0B1)
    • sweep mode (AESA) Not done Not done (by 5H1N0B1)
    • terrain awareness (filtering) Not done Not done (by 5H1N0B1)
    • heat detection based filtering Not done Not done (by 5H1N0B1)
  • select and animate symbols (targets) Not done Not done (by 5H1N0B1)
  • TCAS/transponder awareness for symbol animation/styling Not done Not done (by 5H1N0B1)
  • explore integrating this with Adrian's Radio propagation system via cppbind (Hooray) Not done Not done


Also see ATC-FS and ATC-aircraft

Development

Creating a custom ATC/RADAR Layer

People already having some Nasal experience (property tree, OOP), should be able to complete this 20-30 minutes, and you would end up with an ATC display and a custom symbol - next, you will want to "filter" traffic based on surrrounding features like terrain, altitude, radar settings and radio propagation etc.

If you find yourself getting stuck somewhere, look at some of the other .symbol/.lcontroller and .scontroller files - especially the shorter/simpler ones - and then read the wiki section about adding new layers a few times, and everything will start making sense

Basically, this should get you going quickly:

  • copy an existing set of files (better TFC* instead of VOR*), but name it "TARGET" instead of TFC: TARGET.symbol, TARGET.lcontroller, TARGET.symbol (RADAR may be a more appropriate name)
  • you need to change the name to TARGET inside the 3 new TARGET* files (see the top of the new files, where 'TFC' can be seen)
    • start by opening TARGET.scontroller and replace TFC with TARGET
    • next, open TARGET.symbol - this contains the draw() and update() callbacks to draw/update a single symbol and animate it accordingly, also replace TFC with TARGET
    • next, open TARGET.lcontroller - this contains the layer management code, a layer manages a vector of symbols, replace TFC with TARGET
  • now, add the new files to MapStructure.nas: near the end of MapStructure.nas, in the loading code, there's a vector that contains the names to load, e.g. "VOR", "DME", and "TFC", and so you will need to add "TARGET" there as well. This is to tell the framework about your newly created layer files.
  • once that is done, use the new "TARGET" identifier in the foreach vector in the Nasal section of the dialog seen above and its embedded canvas/nasal/load block
  • this should give you a TCAS traffic (TFC) symbol using the TARGET handle when you run the dialog

(5H1N0B1 : I'm here)

  • once that is working, you can customize the symbol by opening the TARGET.symbol file which contains the draw routine - for example by changing the color or using some custom font/label, or just using a SVG file via parsesevg() - see the NDB.draw file for an example on how to display a custom SVG file
  • at this stage you should have a custom symbol rendered in all the places where MP/AI traffic is located, all driven via the TCAS function.
  • so the next step is to make the code a bit smarter, especially look at the TFC.lcontroller and TFC.scontroller files
  • it may even be a good idea to directly start with those, instead of the VOR files ...
  • once that is working, i.e. 1) you have a custom symbol shown and see all the surrounding MP/AI traffic, you have already successfully created a simple ATC/radar screen!
  • next, you would replace the data source (which is AI/MP traffic) with your own data source - for that, refer to the TARGET (TFC) files to see how the AI/MP traffic is added
  • once you have added your own data source, you can change the heuristics accordingly - to handle terrain, radar range etc.
  • your own objects should ideally be geo.nas Coord objects - that way, the system can directly support your traffic, you only need to derive from geo.Coord to make this happen:
var myPosition3D = {
 new: func {
  return {parents:[geo.Coord.new() ]};
 },
};

Next, you will want to investigate the positionedSearch class, which provides a powerful means to do range-based filtering of positioned objects, these can in turn be filtered (searched) for other characteristics, i.e. based on range, terrain, radio propagation or heat etc. Take a look at TARGET.lcontroller and its searchCmd() helper.

If you need help doing this, please check out the canvas subforum, and also post some screen shots, so that we can better help you It would be a good idea to also start a fgdata topic branch so that we can better track your work and look at the code.

Custom Filtering

Once the previously outlined steps are working, I would consider customizing the searchCmd in your lcontroller file. At the moment, TFC.lcontoller works like this https://gitorious.org/fg/fgdata/source/c78b2f936891bffad49e27d84d952a0d45540eef:Nasal/canvas/map/TFC.lcontroller#

The line where I added PLACEHOLDER is where you could insert your own logic, i.e. the radar-specific stuff from xiii's code - such as checking distance, azimuth, altitude - radar profile/signature, terrain etc - and only append the aircraft if the whole check evaluates to true.

You will find that the whole lcontroller file uses a wrapper called "TrafficModel" - you can either extend this to use your "Target" class directly, or change your Target class accordingly. The main thing is that your class should be derived from a geo.Coord object, so that it has the lat/lon/alt methods available and can be directly processed by MapStructure without requiring further changes.

You will see that the TrafficModel class is used in a few places - so this would need some changes if you use some different approach, but it's still simple. The external interface is all about having lat/lon/alt (positions) and the .equals() method Overall, TrafficModel is just a dumb helper class that is a wrapper for geo.Coord() objects, so there's no reason why you shouldn't be able to extend your own Target class accordingly, you can use the TrafficModel as a template. I would just suggest to maintain the MVC separation at all times.

Analogous to the in_range() helper, you could add other functions for your own filtering needs (altitude, terrain-obstruction etc) - it's better to use separate functions for each, than inflating a single function unnecessarily. As you can see, the in_range() function is unaware of TrafficModel specifics -it deals directly with lat/lon pairs..

So it's simple to reuse as is. TrafficModel itself is typically directly used via the constructor call: .new()

Design

Prototyping

MapStructure.nas

MapStructure.nas currently needs to be patched/edited to add new layers manually - at some point, we will introduce some simple registration API, analogous to the old framework, where people can simply call a function to make their layer known to the framework.

diff --git a/Nasal/canvas/MapStructure.nas b/Nasal/canvas/MapStructure.nas
index 0afdcd2..0c11db3 100644
--- a/Nasal/canvas/MapStructure.nas
+++ b/Nasal/canvas/MapStructure.nas
@@ -480,7 +480,7 @@ var load_MapStructure = func {
                load(FG_ROOT~"/Nasal/canvas/map/"~name~".scontroller", name);
                }
 
-               foreach( var name; ['VOR','FIX','NDB','DME','WPT','TFC'] )
+               foreach( var name; ['VOR','FIX','NDB','DME','WPT','TFC','TARGET',] )
                        load_deps( name );
                load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", name);

TARGET.scontroller

This file contains symbol-specific controller logic

# Class things:
var name = 'TARGET';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[name].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance


# XXX: this is more model-ish than controller-ish
var get_threat_lvl =  func 3; #func(model) (model.getNode("tcas/threat-level").getValue() or 0);
var get_vspd       =  func 300; #func(model) (model.getNode("velocities/vertical-speed-fps").getValue() or 0)*60;
var get_alt_diff   =  func 900; #func(model) (model.getNode("position/altitude-ft").getValue() or 0) - (getprop("/position/altitude-ft") or 0);

TARGET.lcontroller

This file handles managing a single layer

# Class things:
var name = 'TARGET';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
	parents: [SymbolLayer],
	type: name, # Symbol type
	df_controller: __self__, # controller to use by default -- this one
});

var model_root = props.globals.getNode("/ai/models/");


var new = func(layer) {
	var m = {
		parents: [__self__],
		layer: layer,
		listeners: [],
		# query_range_nm: 500,
	};
	# Listen to ai model events
	append(m.listeners, setlistener(
		model_root.getNode("model-added"), func(n) {
			printlog(_MP_dbg_lvl, "Dynamically adding model at "~n.getValue());
			var node = props.globals.getNode(n.getValue());
			var name = node.getName();
			if (name == "aircraft" or name == "multiplayer")
				if (m.in_range(node.getValue("position/latitude-deg"), node.getValue("position/longitude-deg")))
					layer.onAdded(TrafficModel.new(node));
		}
	));
	append(m.listeners, setlistener(
		model_root.getNode("model-removed"), func(n) {
			printlog(_MP_dbg_lvl, "Dynamically deleting model at "~n.getValue());
			var node = props.globals.getNode(n.getValue());
			var name = node.getName();
			if (name == "aircraft" or name == "multiplayer")
				if (m.in_range(node.getValue("position/latitude-deg"), node.getValue("position/longitude-deg")))
					layer.onRemoved(TrafficModel.new(node));
		}
	));
	layer.searcher._equals = func(l,r) l.equals(r);
	return m;
};
var del = func() {
	#print(name~".lcontroller.del()");
	foreach (var l; me.listeners)
		removelistener(l);
};
var in_range = func(lat,lon,myPositionVec=nil,max_dist_m=nil) {
	if (lat == nil or lon == nil) return 0;
	var pos = geo.Coord.new();
	pos.set_latlon(lat,lon);
	var myPosition = geo.Coord.new();
	# FIXME: need a Map Controller for this, and all query_range's/get_position's
	if (myPositionVec == nil)
		var myPositionVec = me.get_position();
	myPosition.set_latlon( myPositionVec[0], myPositionVec[1]);
	if (max_dist_m == nil)
		var max_dist_m = me.query_range()*NM2M;
	return (pos.distance_to( myPosition ) <= max_dist_m )
};

var TrafficModel = {
	new: func(node, id=nil, layer=nil) {
		if (id == nil) id = node.getValue("id");
		var m = {
			# Note: because this inherits from props.Node, Symbol.Controller.equals
			# will call l.equals(r) -- the one defined below
			parents: [TrafficModel, geo.Coord, node], # note we don't implement a full geo.Coord API
			id: id,
			node: node,
			pos: node.getNode("position",1),
		};
		if (m.pos == nil)
			m.latlon = func [nil,nil,nil];
		#debug.dump(m); # why doesn't this print?
		return m;
	},
	equals: func(other) other.id == me.id,
	latlon: func() {
		return [
			me.pos.getValue("latitude-deg"),
			me.pos.getValue("longitude-deg"),
			me.pos.getValue("altitude-ft")
		];
	},
};

##
# huge hack taken from navdisplay.mfd

var query_range = func 500;
var get_position = func geo.aircraft_position().latlon() ;


var searchCmd = func {
	# TODO: this would be a good candidate for splitting across frames
	print("Doing query: "~name);

	var result = [];
	# FIXME: need a Map Controller for this, and all query_range's/get_position's
	var myPositionVec = me.get_position();
	var max_dist_m = me.query_range()*NM2M;

	# AI and Multiplayer traffic
	foreach (var traffic; [model_root.getChildren("aircraft"), model_root.getChildren("multiplayer")]) {
		foreach(var t; traffic) {
			if (me.in_range(t.getValue("position/latitude-deg"),
				            t.getValue("position/longitude-deg"),
				            myPositionVec,
				            max_dist_m))
				append(result, TrafficModel.new(t, nil, me.layer));
		}
	}

	#debug.dump(result);
	#return [];
	print("TARGETS:", size(result) );
	return result;
};

TARGET.symbol

This is the file that handles drawing and updating a single symbol.

# Class things:
var name = 'TARGET';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );

var element_type = "group"; # we want a group, becomes "me.element"
var text_tcas = nil;
var icon_tcas = nil;
var arrow_tcas = [nil,nil];
var arrow_type = nil;

var draw_tcas_arrow = nil;

var draw = func {
	if (me.draw_tcas_arrow == nil)
		me.draw_tcas_arrow = [
			draw_tcas_arrow_above_500,
			draw_tcas_arrow_below_500
		];
	#var callsign = me.model.getNode("callsign").getValue();
	print("Drawing target" );
	var threatLvl = me.controller.get_threat_lvl(me.model);
	var vspeed = me.controller.get_vspd(me.model);
	var altDiff = me.controller.get_alt_diff(me.model);
	# Init
	if (me.text_tcas == nil) {
		me.text_tcas = me.element.createChild("text")
			.setDrawMode( canvas.Text.TEXT )
			.setText(sprintf("%+02.0f",altDiff/100))
			.setFont("LiberationFonts/LiberationSans-Regular.ttf")
			.setColor(1,1,1)
			.setFontSize(28)
			.setAlignment("center-center");
		me.icon_tcas = me.element.createChild("path")
			.setStrokeLineWidth(3);
	}
	# Update
	if (altDiff > 0)
		me.text_tcas.setTranslation(0,-40);
	else
		me.text_tcas.setTranslation(0,40);
	var arrow_type = (vspeed >= 500);
	if (arrow_type != me.arrow_type) {
		(old_type, me.arrow_type) = (me.arrow_type, arrow_type);
		if (old_type != nil and me.arrow_tcas[old_type] != nil) me.arrow_tcas[old_type].hide();
		if (me.arrow_tcas[arrow_type] == nil)
			me.arrow_tcas[arrow_type] = me.draw_tcas_arrow[arrow_type](me.element);
		#me.arrow_tcas[arrow_type].show();
	}
	## TODO: threat level symbols should also be moved to *.draw files
	if (threatLvl == 3) {
		# resolution advisory
		me.icon_tcas.moveTo(-17,-17)
			.horiz(34)
			.vert(34)
			.horiz(-34)
			.close()
			.setColor(1,0,0)
			.setColorFill(1,0,0);
		me.text_tcas.setColor(1,0,0);
		#me.arrow_tcas.setColor(1,0,0);
	} elsif (threatLvl == 2) {
		# traffic advisory
		me.icon_tcas.moveTo(-17,0)
			.arcSmallCW(17,17,0,34,0)
			.arcSmallCW(17,17,0,-34,0)
			.setColor(1,0.5,0)
			.setColorFill(1,0.5,0);
		me.text_tcas.setColor(1,0.5,0);
		#me.arrow_tcas.setColor(1,0.5,0);
	} elsif (threatLvl == 1) {
		# proximate traffic
		me.icon_tcas.moveTo(-10,0)
			.lineTo(0,-17)
			.lineTo(10,0)
			.lineTo(0,17)
			.close()
			.setColor(1,1,1)
			.setColorFill(1,1,1);
	} else {
		print("Not a conflict!");
		# other traffic
		me.icon_tcas.moveTo(-10,0)
			.lineTo(0,-17)
			.lineTo(10,0)
			.lineTo(0,17)
			.close()
			.setColor(1,1,1);
	}
};

traffic-mapstructure.xml (GUI dialog)

This is the XML dialog file that contains an embedded canvas which loads the required MapStructure layers. This is purely used for prototyping and testing, so that there's no aircraft needed to test things.

You will want to save this file in $FG_ROOT/gui/dialogs/traffic-mapstructure.xml.

<?xml version="1.0"?>
<PropertyList>
<name>traffic-mapstructure</name>
<modal>false</modal>
<layout>vbox</layout>
 
<group>
        <layout>hbox</layout>
        <empty><stretch>1</stretch></empty>
 
        <text>
            <label>MapStructure Traffic Demo</label>
        </text>
 
       <empty><stretch>1</stretch></empty>
       <button>
            <legend>Reload</legend>
            <default>1</default>
            <border>2</border>
 
            <binding>
                <command>reinit</command>
                <subsystem>gui</subsystem>
            </binding>
        </button> 
 
 
        <button>
            <pref-width>16</pref-width>
            <pref-height>16</pref-height>
            <legend></legend>
            <default>1</default>
            <keynum>27</keynum>
            <border>2</border>
 
            <binding>
                <command>dialog-close</command>
            </binding>
        </button>
    </group>
    <hrule/>
 
<canvas>
               <name>traffic-map</name>
               <valign>fill</valign>
               <halign>fill</halign>
               <stretch>true</stretch>
               <pref-width>600</pref-width>
               <pref-height>400</pref-height>
<nasal>      
<!-- 
     this is the Canvas-specific Nasal section where you can run your own Nasal code 
     to access and animate the canvas region.
     If you need to access existing code from other places (i.e. aircraft specific stuff), 
     just use io.load_nasal() or io.include() to pull in all required dependencies.
-->
<load><![CDATA[
## 
# you can add your own dependencies here
#
var dependencies = [
];
 
foreach(var path; dependencies)
 io.include( path );
 
var myCanvas = canvas.get( cmdarg() );
var TestMap = myCanvas.createGroup().createChild("map");
TestMap.setController("Aircraft position"); # Nasal/canvas/map/aircraftpos.controller
foreach(var type; var layer_types = ['TARGET',] )
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type); 
# Center the map's origin:
TestMap.setTranslation(300,200); 
# Initialize a range (TODO: LayeredMap.Controller):
TestMap.set("range", 100);
# Little cursor of current position:
TestMap.createChild("path").rect(-5,-5,10,10).setColorFill(1,1,1).setColor(0,1,0);
# And make it move with our aircraft:
TestMap.setController("Aircraft position"); # from aircraftpos.controller

# this is the end of the embedded Canvas/Nasal-load section
]]></load>
<!-- 	all the code here will be executed once the dialog is closed, so you should 
	put your cleanup code here - i.e. free all resources here (timers, listeners, loops)
-->
<unload><![CDATA[
TestMap.del();
]]></unload>
 
</nasal>
</canvas>
 
</PropertyList>

then use the following piece of Nasal to open/show the dialog via the Nasal Console:

gui.showDialog("traffic-mapstructure");