zkv1000 / Nasal / maps / navaids.nas /
Newer Older
535 lines | 18.768kb
add vim foldmarks
Sébastien MARQUE authored on 2017-12-21
1
# vim: set foldmethod=marker foldmarker={{{,}}} :
separates maps code
Sébastien MARQUE authored on 2017-05-11
2
# The following is largely inspired from the Extra500 Avidyne Entegra 9 
3
# https://gitlab.com/extra500/extra500.git
4
# Many thanks to authors: Dirk Dittmann and Eric van den Berg
5
var MapIconCache = {
6
# creates at init an icons cache for navaids, airports and airplane {{{
7
    new : func (svgFile) {
8
        var m = { parents:[MapIconCache] };
9

            
10
        m._canvas = canvas.new( {
11
            'name': 'MapIconCache',
12
            'size': [512, 512],
13
            'view': [512, 512],
14
            'mipmapping': 1
15
        });
16
        m._canvas.addPlacement( {'type': 'ref'} );
17
        m._canvas.setColorBackground(1,1,1,0);
18
        m._group = m._canvas.createGroup('MapIcons');
19

            
20
        canvas.parsesvg(m._group, data.zkv1000_reldir ~ svgFile);
21

            
22
        m._sourceRectMap = {};
23

            
24
        var icons = [ 'airplane' ];
25

            
26
        foreach (var near; [0, 1])
27
            foreach (var surface; [0, 1])
28
                foreach (var tower; [0, 1])
29
                    foreach (var center; tower ? [0, 1] : [ 0 ])
30
                        append(icons, 'Airport_' ~ near ~ surface ~ tower ~ center);
31

            
32
        foreach (var type; ['VOR', 'DME', 'TACAN', 'NDB'])
33
            append(icons, 'Navaid_' ~ type);
34

            
35
        foreach (var i; icons)
36
            m.registerIcon(i);
37

            
38
        return m;
39
    },
40
    registerIcon : func (id) {
41
        me._sourceRectMap[id] = {
42
            'bound' : [],
43
            'size'  : [],
44
        };
45
        var element = me._group.getElementById(id);
46
        if (element != nil) {
47
            me._sourceRectMap[id].bound = element.getTransformedBounds();
48
            # TODO ugly hack ? check for reason!
49
            var top     = 512 - me._sourceRectMap[id].bound[3];
50
            var bottom  = 512 - me._sourceRectMap[id].bound[1];
51
            me._sourceRectMap[id].bound[1] = top;
52
            me._sourceRectMap[id].bound[3] = bottom;
53

            
54
            me._sourceRectMap[id].size = [
55
                me._sourceRectMap[id].bound[2] - me._sourceRectMap[id].bound[0],
56
                me._sourceRectMap[id].bound[3] - me._sourceRectMap[id].bound[1]
57
            ];
58
        }
59
        else {
60
            print('MapIconCache.registerIcon(' ~ id ~ ') fail');
61
        }
62
    },
63
    getBounds : func (id) {
64
        return me._sourceRectMap[id].bound;
65
    },
66
    getSize : func (id) {
67
        return me._sourceRectMap[id].size;
68
    },
69
    boundIconToImage : func (id, image, center=1) {
70
        if (!contains(me._sourceRectMap, id)) {
71
            print('MapIconCache.boundIconToImage('~id~') ... no available.');
72
            id = 'Airport_0001';
73
        }
74
        image.setSourceRect(
75
                me._sourceRectMap[id].bound[0],
76
                me._sourceRectMap[id].bound[1],
77
                me._sourceRectMap[id].bound[2],
78
                me._sourceRectMap[id].bound[3],
79
                0);
80
        image.setSize(
81
                me._sourceRectMap[id].size[0],
82
                me._sourceRectMap[id].size[1]);
83
        if (center) {
84
            image.setTranslation(
85
                    -me._sourceRectMap[id].size[0]/2,
86
                    -me._sourceRectMap[id].size[1]/2);
87
        }
88
    },
89
};
90

            
91
var mapIconCache = MapIconCache.new('Models/MapIcons.svg');
92
# }}}
93

            
94
var MapAirportItem = {
95
# manage airports items by adding the ID and runways on associated icon {{{
96
    new : func (id) {
97
        var m = {parents:[MapAirportItem]};
98
        m._id = id;
99
        m._can = {
100
            'group' : nil,
101
            'label' : nil,
102
            'image' : nil,
103
            'layout': nil,
104
            'runway': [],
105
        };
106
        m._mapAirportIcon = {
107
            'near'      : 0,
108
            'surface'   : 0,
109
            'tower'     : 0,
110
            'center'    : 0,
111
            'displayed' : 0,
112
            'icon'      : '',
113
        };
114
        return m;
115
    },
116
    create : func (group) {
117
        me._can.group = group
118
            .createChild('group', 'airport_' ~ me._id);
119

            
120
        me._can.image = me._can.group.createChild('image', 'airport-image_' ~ me._id)
121
            .setFile(mapIconCache._canvas.getPath())
122
            .setSourceRect(0,0,0,0,0);
123

            
124
        me._can.label = me._can.group.createChild('text', 'airport-label_' ~ me._id)
125
            .setDrawMode( canvas.Text.TEXT )
126
            .setTranslation(0, 37)
correct rotation of navaids ...
Sébastien MARQUE authored on 2021-04-18
127
            .setCenter(0, -37)
separates maps code
Sébastien MARQUE authored on 2017-05-11
128
            .setAlignment('center-bottom-baseline')
129
            .setFont('LiberationFonts/LiberationSans-Regular.ttf')
130
            .setFontSize(24);
131

            
132
        me._can.label.set('fill','#BACBFB');
133
        me._can.label.set('stroke','#000000');
134

            
135
        me._can.layout = group.createChild('group','airport_layout' ~ me._id);
136
        me._can.layoutIcon = group.createChild('group','airport_layout_Icon' ~ me._id);
137
        return me._can.group;
138
    },
139
    draw : func (apt, mapOptions) {
140
        me._mapAirportIcon.near = mapOptions.range > 32 ? 0 : 1;
141
        me._mapAirportIcon.surface  = 0;
142
        me._mapAirportIcon.tower    = 0;
143
        me._mapAirportIcon.center   = 0;
144
        me._mapAirportIcon.displayed    = 0;
145

            
146
        # TODO make departure and destination airports specific
147
        var aptInfo = airportinfo(apt.id);
148

            
149
        me._can.layout.removeAllChildren();
150
        me._can.layoutIcon.removeAllChildren();
151

            
152
        me._mapAirportIcon.tower = (size(aptInfo.comms('tower')) > 0);
153
        me._mapAirportIcon.center = me._mapAirportIcon.tower and (size(aptInfo.comms('approach')) > 0);
154

            
155
        foreach (var rwy; keys(aptInfo.runways)) {
156
            var runway = aptInfo.runways[rwy];
157
            me._mapAirportIcon.surface = MAP_RUNWAY_SURFACE[runway.surface] ? 1 : me._mapAirportIcon.surface;
158
            me._mapAirportIcon.displayed = runway.length > mapOptions.runwayLength ? 1 : me._mapAirportIcon.displayed;
159

            
160
            if (mapOptions.range <= 10) {    # drawing real runways
161
                me._can.layout.createChild('path', 'airport-runway-' ~ me._id ~ '-' ~ runway.id)
162
                    .setStrokeLineWidth(7)
163
                    .setColor(1,1,1)
164
                    .setColorFill(1,1,1)
165
                    .setDataGeo([
166
                        canvas.Path.VG_MOVE_TO,
167
                        canvas.Path.VG_LINE_TO,
168
                        canvas.Path.VG_CLOSE_PATH
169
                    ],[
170
                        'N' ~ runway.lat, 'E' ~ runway.lon,
171
                        'N' ~ runway.reciprocal.lat, 'E' ~ runway.reciprocal.lon,
172
                    ]);
173
            }
174
            elsif (mapOptions.range <= 32) {     #draw icon runways
175
                me._can.layoutIcon.setGeoPosition(apt.lat, apt.lon);
176
                me._can.layoutIcon.createChild('path', 'airport-runway-' ~ me._id ~ '-' ~ runway.id)
177
                    .setStrokeLineWidth(7)
178
                    .setColor(1,1,1)
179
                    .setColorFill(1,1,1)
180
                    .setData([
181
                        canvas.Path.VG_MOVE_TO,
182
                        canvas.Path.VG_LINE_TO,
183
                        canvas.Path.VG_CLOSE_PATH
184
                    ],[
185
                        0, -20,
186
                        0, 20,
187
                    ])
188
                    .setRotation((runway.heading)* D2R);
189
            }
190
        }
191
        me._mapAirportIcon.icon = 'Airport_'
192
            ~ me._mapAirportIcon.near
193
            ~ me._mapAirportIcon.surface
194
            ~ me._mapAirportIcon.tower
195
            ~ me._mapAirportIcon.center;
196

            
197
        if (me._mapAirportIcon.displayed) {
198
            me._can.label
199
                .setText(apt.id)
200
                .setRotation(-mapOptions.orientation * D2R);
201
            me._can.group.setGeoPosition(apt.lat, apt.lon);
202
            if (mapOptions.range <= 10) {
203
                me._can.image.setVisible(0);
204
                me._can.layout.setVisible(1);
205
            }
206
            elsif (mapOptions.range <= 32) {
207
                mapIconCache.boundIconToImage(me._mapAirportIcon.icon, me._can.image);
208
                me._can.image.setVisible(1);
209
                me._can.layout.setVisible(1);
210
            }
211
            else {
212
                mapIconCache.boundIconToImage(me._mapAirportIcon.icon, me._can.image);
213
                me._can.layout.setVisible(0);
214
                me._can.image.setVisible(1);
215
            }
216
            me._can.group.setVisible(1);
217
        }
218
        return me._mapAirportIcon.displayed;
219
    },
220
    update : func (mapOptions) {
221
        if (mapOptions.range <= 10) { }
222
        elsif (mapOptions.range <= 32)
223
            me._can.layoutIcon.setRotation(-mapOptions.orientation * D2R);
224
        else { }
225
    },
226
    setVisible : func (visibility) {
227
        me._can.group.setVisible(visibility);
228
        me._can.layout.setVisible(visibility);
229
        me._can.image.setVisible(visibility);
230
        me._can.layoutIcon.setVisible(visibility);
231
    },
232
};
233
# }}}
234

            
235
var MapNavaidItem = {
236
# manage navaids items by adding ID in the icon {{{
237
    new : func (id, type) {
238
        var m = {parents:[MapNavaidItem]};
239
        m._id = id;
240
        m._type = type;
241
        m._can = {
242
            'group' : nil,
243
            'label' : nil,
244
            'image' : nil,
245
        };
246
        return m;
247
    },
248
    create : func (group) {
249
        me._can.group = group
250
            .createChild('group', me._type ~ '_' ~ me._id);
251

            
252
        me._can.image = me._can.group.createChild('image', me._type ~ '-image_' ~ me._id)
253
            .setFile(mapIconCache._canvas.getPath())
254
            .setSourceRect(0,0,0,0,0);
255

            
256
        me._can.label = me._can.group.createChild('text', me._type ~ '-label_' ~ me._id)
257
            .setDrawMode( canvas.Text.TEXT )
258
            .setTranslation(0,42)
correct rotation of navaids ...
Sébastien MARQUE authored on 2021-04-18
259
            .setCenter(0, -42)
separates maps code
Sébastien MARQUE authored on 2017-05-11
260
            .setAlignment('center-bottom-baseline')
261
            .setFont('LiberationFonts/LiberationSans-Regular.ttf')
lower text size for map nava...
Sébastien MARQUE authored on 2020-05-02
262
            .setFontSize(16);
separates maps code
Sébastien MARQUE authored on 2017-05-11
263

            
264
        me._can.label.set('fill','#BACBFB');
265
        me._can.label.set('stroke','#000000');
266

            
267
        return me._can.group;
268
    },
269
    setData : func (navaid, type, mapOptions) {
270
        mapIconCache.boundIconToImage('Navaid_' ~ type, me._can.image);
271
        me._can.label
272
            .setText(navaid.id)
273
            .setRotation(-mapOptions.orientation * D2R);
274
        me._can.group.setGeoPosition(navaid.lat, navaid.lon);
275
    },
276
    setVisible : func (visibility) {
277
        me._can.group.setVisible(visibility);
278
    },
279
};
280
# }}}
281

            
282
var MapAirplaneItem = {
283
# set airplane on ND {{{
284
    new : func {
285
        var m = {parents:[MapAirplaneItem]};
286
        m._can = {
287
            'group' : nil,
288
            'image' : nil,
289
        };
290
        return m;
291
    },
292
    create : func (group) {
293
        me._can.group = group
294
            .createChild('group', 'airplane');
295
        me._can.image = me._can.group.createChild('image', 'airplane-image')
296
            .setFile(mapIconCache._canvas.getPath())
297
            .setSourceRect(0,0,0,0,0);
298
        return me._can.group;
299
    },
300
    setData : func (orientation) {
301
        mapIconCache.boundIconToImage('airplane', me._can.image);
302
        me._can.group
303
            .setGeoPosition(data.lat, data.lon)
304
            .setRotation(orientation * D2R);
305
    },
306
    setVisible : func (visibility) {
307
        me._can.group.setVisible(visibility);
308
    },
309
};
310
# }}}
311

            
312
var MAP_RUNWAY_SURFACE =  {0:0, 1:1, 2:1, 3:0, 4:0, 5:0, 6:1, 7:1, 8:0, 9:0, 10:0, 11:0, 12:0};
313
var MAP_RUNWAY_AT_RANGE = func (range) {
314
    if (range < 40) return 0;
315
    if (range < 50) return 250;
316
    if (range < 80) return 500;
317
    if (range < 160) return 1000;
318
    if (range < 240) return 3000;
319
    return 3000;
320
}
321
var MAP_TXRANGE_VOR = func (range) {
322
    if (range < 40) return 0;
323
    if (range < 50) return 20;
324
    if (range < 80) return 25;
325
    if (range < 160) return 30;
326
    if (range < 240) return 50;
327
    return 100;
328
}
329
####
330
# Declutter
331
#   land
332
#       0 : 'Terrain'
333
#       1 : 'Political boundaries'
334
#       2 : 'River/Lakes/Oceans'
335
#       3 : 'Roads'
336
#   Nav
337
#       0 : 'Airspace'
338
#       1 : 'Victor/Jet airways'
339
#       2 : 'Obstacles'
340
#       3 : 'Navaids'
341

            
342
var MapNavaids = {
343
# the layer to show navaids, airports and airplane symbol {{{
344
    new : func (device, group) {
345
        var m = {parents : [MapNavaids]};
346

            
347
        m._model = nil;
348
        m.device = device;
349
        m._visibility = m.device.role == 'MFD';
350

            
351
        m._group = group.createChild('map', 'MFD map')
352
            .setTranslation(
353
                m.device.role == 'MFD' ? (m.device.data.mapview[0] + m.device.data.mapclip.left)/2 : 120,
354
                m.device.role == 'MFD' ? 400 : 600)
close issue with misplacing ...
Sébastien MARQUE authored on 2020-06-22
355
            .setRange(m.device.data['range-nm']/2)
separates maps code
Sébastien MARQUE authored on 2017-05-11
356
            .setVisible(m._visibility);
357

            
358
        m._can = {};
359
        m._cache = {};
360
        foreach (var n; ['airport', 'VOR', 'TACAN', 'NDB', 'DME']) {
361
            m._can[n] = m._group.createChild('group', n);
362
            m._cache[n] = {
363
                'data' : [],
364
                'index' : 0,
365
                'max' : 100,
366
            };
367
        }
368
        m._can['airplane'] = m._group.createChild('group', 'airplane');
369
        m._cache['airplane'] = {
370
            displayed : 0,
371
            item : nil,
372
        };
373

            
374
        m._mapOptions = {
375
            declutterLand : 3,
376
            declutterNAV  : 3,
377
            lightning     : 0,
378
            reports       : 0,
379
            overlay       : 0,
close issue with misplacing ...
Sébastien MARQUE authored on 2020-06-22
380
            range         : m.device.data['range-nm'] / 2,
separates maps code
Sébastien MARQUE authored on 2017-05-11
381
            runwayLength  : -1,
382
            orientation   : m.device.data.orientation.map,
383
        };
384

            
385
        m._results = nil;
386

            
improve the selection of dis...
Sébastien MARQUE authored on 2017-12-30
387
        if (m.device.role == 'PFD') {
388
            m.device.softkeys.colored.INSETNAVAIDSALL = 1;
389
            m.device.softkeys.colored.INSETNAVAIDSTACAN = 1;
390
            m.device.softkeys.colored.INSETNAVAIDSVOR = 1;
391
            m.device.softkeys.colored.INSETNAVAIDSNDB = 1;
392
            m.device.softkeys.colored.INSETNAVAIDSDME = 1;
393
            m.device.softkeys.colored.INSETNAVAIDSAPT = 1;
fix navaids softkey display
Sébastien MARQUE authored on 2021-04-11
394
            m.device.softkeys.colored.INSETNAVAIDS = 1;
improve the selection of dis...
Sébastien MARQUE authored on 2017-12-30
395
        }
396
        if (m.device.role == 'MFD') {
397
            m.device.softkeys.colored.MAPNAVAIDSALL = 1;
398
            m.device.softkeys.colored.MAPNAVAIDSTACAN = 1;
399
            m.device.softkeys.colored.MAPNAVAIDSVOR = 1;
400
            m.device.softkeys.colored.MAPNAVAIDSNDB = 1;
401
            m.device.softkeys.colored.MAPNAVAIDSDME = 1;
402
            m.device.softkeys.colored.MAPNAVAIDSAPT = 1;
fix navaids softkey display
Sébastien MARQUE authored on 2021-04-11
403
            m.device.softkeys.colored.MAPNAVAIDS = 1;
improve the selection of dis...
Sébastien MARQUE authored on 2017-12-30
404
        }
405

            
separates maps code
Sébastien MARQUE authored on 2017-05-11
406
        return m;
407
    },
poweroff improved
Sébastien MARQUE authored on 2020-05-16
408
    off: func {
409
        me._group.setVisible(0);
410
        me._group.removeAllChildren();
411
    },
separates maps code
Sébastien MARQUE authored on 2017-05-11
412
    update : func {
413
        me._group._node.getNode('ref-lat', 1).setDoubleValue(data.lat);
414
        me._group._node.getNode('ref-lon', 1).setDoubleValue(data.lon);
415

            
close issue with misplacing ...
Sébastien MARQUE authored on 2020-06-22
416
        me._group.setRange(me.device.data['range-nm']/2);
separates maps code
Sébastien MARQUE authored on 2017-05-11
417
        me._mapOptions.orientation = me.device.data.orientation.map;
418

            
419
        if (me._visibility == 1) {
420
            me.loadAirport();
421
            foreach (var n; ['VOR', 'TACAN', 'NDB', 'DME'])
422
                me.loadNavaid(n);
423
            me.loadAirplane();
424
        }
425
    },
426
    _onVisibilityChange : func {
427
        me._group.setVisible(me._visibility);
428
    },
429
    setMapOptions : func (mapOptions) {
430
        me._mapOptions = mapOptions;
431
        me.update();
432
    },
433
    updateOrientation : func (value) {
434
        me._mapOptions.orientation = value;
435
        for (var i = 0 ; i < me._cache.airport.index ; i +=1) {
436
            item = me._cache.airport.data[i];
437
            item.update(me._mapOptions);
438
        }
439
    },
440
    setRotation : func (deg) {
441
        me._group.setRotation(deg * D2R);
442
    },
443
    setVisible : func (v) {
444
        if (me._visibility != v) {
445
            me._visibility = v;
446
            me._onVisibilityChange();
447
        }
448
    },
449
    _onVisibilityChange : func {
450
        me._group.setVisible(me._visibility);
451
    },
452
    # positioned.findWithinRange : any, fix, vor, ndb, ils, dme, tacan
453

            
454
    loadAirport : func {
455
        me._cache.airport.index = 0;
456
        var results = positioned.findWithinRange(me._mapOptions.range * 2.5, 'airport');
457
        var item = nil;
458

            
459
        if (me._mapOptions.declutterNAV >= 2)
460
            me._mapOptions.runwayLength = MAP_RUNWAY_AT_RANGE(me._mapOptions.range);
461
        elsif (me._mapOptions.declutterNAV >= 1)
462
            me._mapOptions.runwayLength = 2000;
463
        else
464
            me._mapOptions.runwayLength = 3000;
465

            
466
        if (me._mapOptions.runwayLength >= 0) {
467
            foreach (var apt; results) {
468
                if (me._cache.airport.index >= me._cache.airport.max )
469
                    break;
470

            
471
                if (size(me._cache.airport.data) > me._cache.airport.index)
472
                    item = me._cache.airport.data[me._cache.airport.index];
473
                else {
474
                    item = MapAirportItem.new(me._cache.airport.index);
475
                    item.create(me._can.airport);
476
                    append(me._cache.airport.data, item);
477
                }
478

            
479
                if (item.draw(apt, me._mapOptions)) {
480
                    item.setVisible(1);
481
                    me._cache.airport.index += 1;
482
                }
483
            }
484
        }
485

            
486
        for (var i = me._cache.airport.index ; i < size(me._cache.airport.data) ; i +=1) {
487
            item = me._cache.airport.data[i];
488
            item.setVisible(0);
489
        }
490
    },
491
    loadNavaid : func (type) {
492
        me._cache[type].index = 0;
493
        if (me._mapOptions.declutterNAV >= 3) { # TODO test for DME and NDB range < 100nm
494
            var range = me._mapOptions.range * 2.5;
495
            var txRange = MAP_TXRANGE_VOR(me._mapOptions.range);
496
            var results = positioned.findWithinRange(range, type);
497
            var item = nil;
498
            foreach (var n; results) {
499
                if (n.range_nm < txRange)
500
                    break;
501

            
502
                if (me._cache[type].index >= me._cache[type].max )
503
                    break;
504

            
505
                if (size(me._cache[type].data) > me._cache[type].index) {
506
                    item = me._cache[type].data[me._cache[type].index];
507
                    item.setData(n, type, me._mapOptions);
508
                }
509
                else {
510
                    item = MapNavaidItem.new(me._cache[type].index, type);
511
                    item.create(me._can[type]);
512
                    item.setData(n, type, me._mapOptions);
513
                    append(me._cache[type].data, item);
514
                }
515
                item.setVisible(1);
516
                me._cache[type].index += 1;
517
            }
518
        }
519
        for (var i = me._cache[type].index ; i < size(me._cache[type].data) ; i +=1) {
520
            item = me._cache[type].data[i];
521
            item.setVisible(0);
522
        }
523
    },
524
    loadAirplane : func {
525
        if (!me._cache.airplane.displayed) {
526
            me._cache.airplane.item = MapAirplaneItem.new();
527
            me._cache.airplane.item.create(me._can['airplane']);
528
            me._cache.airplane.displayed = 1;
529
        }
530
        me._cache.airplane.item.setData(me.device.data.orientation.airplane);
531
        me._cache.airplane.item.setVisible(1);
532
    },
533
};
534
# }}}
535