Showing 5 changed files with 402 additions and 1 deletions
+18
Nasal/core.nas
... ...
@@ -46,6 +46,24 @@ var setListeners = func {
46 46
             func foreach (var c; keys(flightdeck))
47 47
                 if (flightdeck[c].role == 'PFD')
48 48
                     flightdeck[c].display.updateBARO(), 0, 2);
49
+
50
+    prop = '/autopilot/route-manager/signals/edited';
51
+    data.listeners[prop] = setlistener(prop,
52
+            func foreach (var c; keys(flightdeck))
53
+                flightdeck[c].map.layers.route.onFlightPlanChange(),
54
+                0, 1);
55
+
56
+    prop = '/autopilot/route-manager/current-wp';
57
+    data.listeners[prop] = setlistener(prop,
58
+            func (n) foreach (var c; keys(flightdeck))
59
+                    flightdeck[c].map.layers.route.onCurrentWaypointChange(n),
60
+                0, 1);
61
+
62
+    prop = '/autopilot/route-manager/active';
63
+    data.listeners[prop] = setlistener(prop,
64
+            func foreach (var c; keys(flightdeck))
65
+                    flightdeck[c].map.layers.route.onCurrentWaypointChange(props.globals.getNode('/autopilot/route-manager/current-wp')),
66
+                0, 1);
49 67
 }
50 68
 
51 69
 var deviceClass = {
+1
Nasal/map.nas
... ...
@@ -34,6 +34,7 @@ var mapClass = {
34 34
 
35 35
         m.layers = {};
36 36
         m.layers.tiles = MapTiles.new(m.device, m.group);
37
+        m.layers.route = MapRoute.new(m.device, m.group);
37 38
         m.layers.navaids = MapNavaids.new(m.device, m.group);
38 39
 
39 40
 #        m.device.display.display.createGroup().createChild('path')
+380
Nasal/maps/route.nas
... ...
@@ -0,0 +1,380 @@
1
+# The following is largely inspired from the Extra500 Avidyne Entegra 9 
2
+# https://gitlab.com/extra500/extra500.git
3
+# Many thanks to authors: Dirk Dittmann and Eric van den Berg
4
+var RouteItemClass = {
5
+    new : func (canvasGroup, index) {
6
+        var m = {parents:[RouteItemClass]};
7
+        m.index = index;
8
+        m.group = canvasGroup.createChild('group', 'Waypoint-' ~ index).setVisible(0);
9
+        
10
+        m.can = {
11
+            Waypoint : m.group.createChild('path','icon-' ~ index)
12
+                .setStrokeLineWidth(3)
13
+                .setScale(1)
14
+                .setColor(1,1,1)
15
+                .setColorFill(1,1,1)
16
+                .moveTo(-20, 0)
17
+                .lineTo(-5, 5)
18
+                .lineTo(0, 20)
19
+                .lineTo(5, 5)
20
+                .lineTo(20, 0)
21
+                .lineTo(5, -5)
22
+                .lineTo(0, -20)
23
+                .lineTo(-5, -5)
24
+                .close(),
25
+            Label : m.group.createChild('text', 'wptLabel-' ~ index)
26
+                .setFont('LiberationFonts/LiberationMono-Bold.ttf')
27
+                .setTranslation(25,-25)
28
+                .setFontSize(20, 1)
29
+                .setColor(1,1,1)
30
+                .setColorFill(1,1,1),
31
+            track : canvasGroup.createChild('path','track-' ~ index)
32
+                .setStrokeLineWidth(3)
33
+                .setScale(1)
34
+                .setColor(1,1,1)
35
+                .setVisible(1),
36
+        };
37
+        return m;
38
+    },
39
+    setVisible : func (v) {
40
+        me.group.setVisible(v);
41
+        me.can.track.setVisible(v);
42
+    },
43
+    draw : func (wpt) {
44
+        me.can.Label.setText(wpt[0].name);
45
+        me.setColor(1,1,1);
46
+        me.group.setGeoPosition(wpt[0].lat, wpt[0].lon);
47
+        me.group.setVisible(1);
48
+    },
49
+    drawTrack : func (wpt) {
50
+        var cmds = [];
51
+        var coords = [];
52
+        var cmd = canvas.Path.VG_MOVE_TO;
53
+        me.can.track.setVisible(1);
54
+        
55
+        foreach (var pt; wpt) {
56
+            append(coords, 'N' ~ pt.lat);
57
+            append(coords, 'E' ~ pt.lon);
58
+            append(cmds, cmd);
59
+            cmd = canvas.Path.VG_LINE_TO;
60
+        }
61
+        me.can.track.setDataGeo(cmds, coords);
62
+    },
63
+    setColor : func (color) {
64
+        me.can.Label.setColor(color).setColorFill(color);
65
+        me.can.Waypoint.setColor(color).setColorFill(color);
66
+    },
67
+    del : func {
68
+        me.group.del();
69
+        me = nil;
70
+    },
71
+};
72
+
73
+var FMSIcon = {
74
+    new : func (canvasGroup, text) {
75
+        var m = {parents:[ FMSIcon ]};
76
+        m.group = canvasGroup.createChild('group', 'FMS-' ~ text).setVisible(0);   
77
+        m.can = {
78
+            icon : m.group.createChild('path','FMS-icon' ~ text)
79
+                .setStrokeLineWidth(3)
80
+                .setScale(1)
81
+                .setColor(0,1,0)
82
+                .setColorFill(0,1,0)
83
+                .moveTo(-15, 0)
84
+                .lineTo(0, 15)
85
+                .lineTo(15, 0)
86
+                .lineTo(0, -15)
87
+                .close(),
88
+            label : m.group.createChild('text', 'FMS-label-' ~ text)
89
+                .setFont('LiberationFonts/LiberationMono-Bold.ttf')
90
+                .setTranslation(20,12)
91
+                .setFontSize(32, 1)
92
+                .setColor(0,1,0)
93
+                .setColorFill(0,1,0)
94
+                .setText(text),
95
+        };
96
+        return m;
97
+    },
98
+    setVisible : func (v)
99
+        me.group.setVisible(v),
100
+    setGeoPosition : func (lat, lon)
101
+        me.group.setGeoPosition(lat, lon),
102
+};
103
+
104
+var FMSIconRTA = {
105
+    new : func (canvasGroup, text) {
106
+        var m = {parents:[FMSIconRTA]};
107
+        m.group = canvasGroup.createChild('group', 'FMS-' ~ text).setVisible(0);   
108
+        m.can = {
109
+            icon : m.group.createChild('path','FMS-icon' ~ text)
110
+                .setStrokeLineWidth(3)
111
+                .setScale(1)
112
+                .setColor(0,1,0)
113
+                .setColorFill(0,1,0)
114
+                .moveTo(-15, 0)
115
+                .lineTo(0, 15)
116
+                .lineTo(15, 0)
117
+                .lineTo(0, -15)
118
+                .close(),
119
+            label : m.group.createChild('text', 'FMS-label-' ~ text)
120
+                .setFont('LiberationFonts/LiberationMono-Bold.ttf')
121
+                .setTranslation(-80,12)
122
+                .setFontSize(32, 1)
123
+                .setColor(0,1,0)
124
+                .setColorFill(0,1,0)
125
+                .setText(text),
126
+        };
127
+        return m;
128
+    },
129
+    setVisible : func (v)
130
+        me.group.setVisible(v),
131
+    setGeoPosition : func (lat, lon)
132
+        me.group.setGeoPosition(lat, lon),
133
+};
134
+
135
+var MapRoute = {
136
+    new : func (device, group) {
137
+        var m = {parents:[ MapRoute ]}; 
138
+        m.item       = [];
139
+        m.itemIndex  = 0;
140
+        m.visibility = 0;
141
+        m.device = device;
142
+        m.group = group.createChild('map', 'route map')
143
+            .setTranslation(
144
+                m.device.role == 'MFD' ? (m.device.data.mapview[0] + m.device.data.mapclip.left)/2 : 120,
145
+                m.device.role == 'MFD' ? 400 : 600)
146
+            .setVisible(m.visibility);
147
+        m.group._node
148
+            .getNode('range', 1).setDoubleValue(m.device.data['range-factor']);
149
+        m.group._node
150
+            .getNode('ref-lat', 1).setDoubleValue(data.lat);
151
+        m.group._node
152
+            .getNode('ref-lon', 1).setDoubleValue(data.lon);
153
+
154
+        m.groupTrack = m.group.createChild('group', 'Track')
155
+            .setVisible(1);
156
+        m.groupOBS   = m.group.createChild('group', 'OBS')
157
+            .setVisible(0);
158
+        m.groupFMS   = m.group.createChild('group', 'FMS')
159
+            .setVisible(1);
160
+        
161
+        m.can = {
162
+            track : m.group.createChild('path', 'track')
163
+                .setStrokeLineWidth(3)
164
+                .setScale(1)
165
+                .setColor(1,1,1),
166
+            currentLeg : m.groupFMS.createChild('path', 'currentLeg')
167
+                .setStrokeLineWidth(5)
168
+                .setScale(1)
169
+                .setColor(1,0,1),
170
+            nextLeg : m.groupFMS.createChild('path', 'nextLeg')
171
+                .setStrokeLineWidth(5)
172
+                .setScale(1)
173
+                .setStrokeDashArray([25,25])
174
+                .setColor(1,0,1),
175
+            obsCourse : m.groupOBS.createChild('path', 'obsCourse')
176
+                .setStrokeLineWidth(5)
177
+                .setScale(1)
178
+                .setColor(1,0,1),
179
+        };
180
+        
181
+        m.TOD = FMSIcon.new(m.groupFMS, 'TOD');
182
+        m.TOC = FMSIcon.new(m.groupFMS, 'TOC');
183
+        m.RTA = FMSIconRTA.new(m.groupFMS, 'RTA');
184
+        
185
+        m.track = {
186
+            cmds  : [],
187
+            coords: [],
188
+        };
189
+        m.currentLeg = {
190
+            cmds: [ canvas.Path.VG_MOVE_TO, canvas.Path.VG_LINE_TO ],
191
+            coords: [0, 0, 0, 0],
192
+            index : -1,
193
+        };
194
+        m.nextLeg = {
195
+            cmds: [ canvas.Path.VG_MOVE_TO, canvas.Path.VG_LINE_TO ],
196
+            coords: [0, 0, 0, 0],
197
+            index : -1,
198
+        };
199
+        
200
+        m.mapOptions = {
201
+            orientation : 0,
202
+        };
203
+        
204
+        m.obsMode = 0;
205
+        m.obsCourse = 0;
206
+        m.obsWaypoint = RouteItemClass.new(m.groupOBS, 'obs_wp');
207
+        m.obsCourseData = {
208
+            cmds: [ canvas.Path.VG_MOVE_TO, canvas.Path.VG_LINE_TO ],
209
+            coords: [0, 0, 0, 0]};
210
+
211
+        m.flightPlan = [];
212
+        m.currentWpIndex = getprop('/autopilot/route-manager/current-wp');
213
+        
214
+        return m;
215
+    },
216
+    update: func {
217
+        me.visibility != 0 or return;
218
+        me.group._node
219
+            .getNode('range', 1).setDoubleValue(me.device.data['range-factor']);
220
+        me.group._node.getNode('ref-lat', 1).setDoubleValue(data.lat);
221
+        me.group._node.getNode('ref-lon', 1).setDoubleValue(data.lon);
222
+    },
223
+    setVisible : func (v) {
224
+        if (me.visibility != v) {
225
+            me.visibility = v;
226
+            me.group.setVisible(v);
227
+        }
228
+    },
229
+    updateOrientation : func (value) {
230
+        me.mapOptions.orientation = value;
231
+        me.can.obsCourse.setRotation((me.obsCourse - me.mapOptions.orientation) * D2R);
232
+    },
233
+    onFlightPlanChange : func {
234
+        for (var i = size(me.item) - 1; i >= 0; i -= 1) {
235
+            me.item[i].del();
236
+            pop(me.item);
237
+        }
238
+        me.itemIndex = 0;
239
+        me.flightPlan = [];
240
+        var route = props.globals.getNode('/autopilot/route-manager/route');
241
+        var planSize = getprop('/autopilot/route-manager/route/num');
242
+        for (var i=0; i < planSize - 1; i+=1) {
243
+            var wp0  = route.getNode('wp[' ~   i   ~ ']');
244
+            var wp1  = route.getNode('wp[' ~ (i+1) ~ ']');
245
+            append(me.flightPlan, [
246
+                {
247
+                    lat : wp0.getNode('latitude-deg').getValue(),
248
+                    lon : wp0.getNode('longitude-deg').getValue(),
249
+                    name: wp0.getNode('id').getValue(),
250
+                },
251
+                {
252
+                    lat : wp1.getNode('latitude-deg').getValue(),
253
+                    lon : wp1.getNode('longitude-deg').getValue(),
254
+                    name: wp1.getNode('id').getValue(),
255
+                }
256
+            ]);
257
+            append(me.item, RouteItemClass.new(me.groupTrack, i));
258
+        }
259
+        me.setVisible(me.device.map.visibility);
260
+        me.drawWaypoints();
261
+    },
262
+    onCurrentWaypointChange : func (n) {
263
+        me.currentWpIndex = n.getValue();
264
+        me.currentLeg.index = me.currentWpIndex - 1;
265
+        me.nextLeg.index = me.currentWpIndex;
266
+        if (me.currentWpIndex == 0) {
267
+            n.setIntValue(1);
268
+            me.currentWpIndex = 1;
269
+        }
270
+        me.drawLegs();
271
+    },
272
+    drawWaypoints : func {
273
+        me.visibility != 0 or return;
274
+        var cmd = canvas.Path.VG_MOVE_TO;
275
+
276
+        for (var i=0; i < size(me.flightPlan); i+=1) {
277
+#            me.item[me.itemIndex].draw(me.flightPlan[i]);
278
+            me.item[me.itemIndex].drawTrack(me.flightPlan[i]);
279
+            me.itemIndex +=1;
280
+        }
281
+        me.groupTrack.setVisible(me.visibility and (me.obsMode == 0));
282
+    },
283
+    drawLegs : func {
284
+        me.visibility != 0 or return;
285
+        if (me.currentLeg.index >= 0 and me.currentLeg.index < size(me.flightPlan)) {
286
+            var cmd = canvas.Path.VG_MOVE_TO;
287
+            me.currentLeg.coords = [];
288
+            me.currentLeg.cmds = [];
289
+            foreach (var pt; me.flightPlan[me.currentLeg.index]) {
290
+                append(me.currentLeg.coords, 'N' ~ pt.lat);
291
+                append(me.currentLeg.coords, 'E' ~ pt.lon);
292
+                append(me.currentLeg.cmds, cmd);
293
+                cmd = canvas.Path.VG_LINE_TO;
294
+            }
295
+            me.can.currentLeg.setDataGeo(me.currentLeg.cmds, me.currentLeg.coords);
296
+            me.can.currentLeg.setVisible(1);
297
+        }
298
+        else
299
+            me.can.currentLeg.setVisible(0);
300
+        
301
+        if (me.nextLeg.index >= 1 and me.nextLeg.index < size(me.flightPlan)) {
302
+            var cmd = canvas.Path.VG_MOVE_TO;
303
+            me.nextLeg.coords = [];
304
+            me.nextLeg.cmds = [];
305
+            foreach (var pt; me.flightPlan[me.nextLeg.index]) {
306
+                append(me.nextLeg.coords,'N' ~ pt.lat);
307
+                append(me.nextLeg.coords,'E' ~ pt.lon);
308
+                append(me.nextLeg.cmds,cmd);
309
+                cmd = canvas.Path.VG_LINE_TO;
310
+            }
311
+            me.can.nextLeg.setDataGeo(me.nextLeg.cmds, me.nextLeg.coords);
312
+            me.can.nextLeg.setVisible(1);
313
+        }
314
+        else
315
+            me.can.nextLeg.setVisible(0);
316
+    },
317
+#    _onVisibilityChange : func {
318
+#        me.group.setVisible(me.visibility and (me.obsMode == 0));
319
+#        me.groupTrack.setVisible(me.visibility and (me.obsMode == 0));
320
+#        me.groupFMS.setVisible(me.visibility and (me.obsMode == 0));
321
+#        me.groupOBS.setVisible(me.visibility and (me.obsMode == 1));
322
+#        me.TOD.setVisible(fms._dynamicPoint.TOD.visible and me.visibility);
323
+#        me.TOC.setVisible(fms._dynamicPoint.TOC.visible and me.visibility);
324
+#        me.RTA.setVisible(fms._dynamicPoint.RTA.visible and me.visibility);
325
+#    },
326
+#    _onFplReadyChange : func (n) {
327
+#        me.TOD.setVisible(fms._dynamicPoint.TOD.visible and me.visibility);
328
+#        me.TOC.setVisible(fms._dynamicPoint.TOC.visible and me.visibility);
329
+#        me.RTA.setVisible(fms._dynamicPoint.RTA.visible and me.visibility);
330
+#    },
331
+#    _onFplUpdatedChange : func (n) {
332
+#        if (fms._dynamicPoint.TOD.visible)
333
+#            me.TOD.setGeoPosition(fms._dynamicPoint.TOD.position.lat, fms._dynamicPoint.TOD.position.lon);
334
+#        if (fms._dynamicPoint.TOC.visible)
335
+#            me.TOC.setGeoPosition(fms._dynamicPoint.TOC.position.lat, fms._dynamicPoint.TOC.position.lon);
336
+#        if (fms._dynamicPoint.RTA.visible)
337
+#            me.RTA.setGeoPosition(fms._dynamicPoint.RTA.position.lat, fms._dynamicPoint.RTA.position.lon);  
338
+#        
339
+#        me.TOD.setVisible(fms._dynamicPoint.TOD.visible and me.visibility);
340
+#        me.TOC.setVisible(fms._dynamicPoint.TOC.visible and me.visibility);
341
+#        me.RTA.setVisible(fms._dynamicPoint.RTA.visible and me.visibility);
342
+#    },
343
+#    _onObsModeChange : func (n) {
344
+#        me.obsMode = n.getValue();
345
+#        
346
+#        if (me.obsMode==1) {
347
+#            var wp = {
348
+#                wp_lat : getprop('/instrumentation/gps[0]/scratch/latitude-deg'),
349
+#                wp_lon : getprop('/instrumentation/gps[0]/scratch/longitude-deg'),
350
+#                wp_name : getprop('/instrumentation/gps[0]/scratch/ident'),
351
+#            };
352
+#            var acLat = getprop('/position/latitude-deg');
353
+#            var acLon = getprop('/position/longitude-deg');
354
+#            
355
+#            me.groupOBS.setGeoPosition(wp.wp_lat, wp.wp_lon);
356
+#            
357
+#            me.obsCourseData.coords = [
358
+#                0,
359
+#                0,
360
+#                0,
361
+#                5000,
362
+#            ];
363
+#            
364
+#            me.obsWaypoint._can.Label.setText(wp.wp_name);
365
+#            me.obsWaypoint.setColor(1,0,1);
366
+#            me.obsWaypoint._group.setVisible(1);
367
+#            
368
+#            me.can.obsCourse.setData(me.obsCourseData.cmds,me.obsCourseData.coords);
369
+#        }
370
+#        
371
+#        me.groupTrack.setVisible(me.visibility and (me.obsMode == 0));
372
+#        me.groupFMS.setVisible(me.visibility and (me.obsMode == 0));
373
+#        me.groupOBS.setVisible(me.visibility and (me.obsMode == 1));
374
+#    },
375
+#    _onObsCourseChange : func (n) {
376
+#        me.obsCourse = n.getValue();
377
+#        me.can.obsCourse.setRotation((me.obsCourse - me.mapOptions.orientation) * global.CONST.DEG2RAD);
378
+#    },
379
+};
380
+
+2 -1
README.md
... ...
@@ -26,7 +26,7 @@ ZKV1000 just after booting, installed in the Lancair 235 (all other instruments
26 26
 # Thanks
27 27
 Thanks to [www2](https://forum.flightgear.org/viewtopic.php?f=14&t=25291) the modeller of the [GDU104X project "Farmin/G1000"](http://wiki.flightgear.org/Project_Farmin/FG1000) I can continue the work began many years ago with a nicer (and working) 3D model instrument. Thanks to him/her for the SVG basis file too.  
28 28
 Thanks to Hooray's nice efforts, and some examples ans snipsets of code here and there from the Canvas team.  
29
-Thanks to all FlightGear community for the funny project I let too many years off side...
29
+Thanks to all FlightGear community for the funny project I let too many years off side... Especially to authors of Avidyne Entegra 9 on extra500, from which the map is largely inspired (and many lines of code copied and adapted): Dirk Dittmann and Eric van den Berg 
30 30
 
31 31
 # Origin
32 32
 The first ZKV1000, which was completly XML animated, was completly abandonned. Moreover the Nasal code became unmaintanable from my point of view. Not sure this one is better, but I think
... ...
@@ -84,6 +84,7 @@ Please report bug at <seb.marque@free.fr>.
84 84
   * Bearing needs some checks to be sure it shows the correct information
85 85
   * XPDR: emergency code depending of the country (eg.: 1200 for US, 7700 for Europe), should be set in settings
86 86
 * ![][80%]
87
+  * route displayed on map: legs ![][done], current and next leg ![][done], TOC/TOD ![][ongoing], OBS ![][ongoing]
87 88
 * ![][70%]
88 89
 * ![][60%]
89 90
   * NOT TESTED: add the posssibility to only use Nasal part of the instrument. This in case of a cockpit which already includes needed objects and animations for screens, knobs and buttons in its config files
+1
zkv1000.nas
... ...
@@ -6,6 +6,7 @@ files_to_load = [
6 6
     'knobs.nas',   # handles knobs
7 7
     'buttons.nas', # handles knobs and buttons
8 8
     'softkeys.nas',# handles softkeys and menus items
9
+    'maps/route.nas',
9 10
     'maps/navaids.nas',
10 11
     'maps/tiles.nas',
11 12
     'map.nas',     # moves the maps