zkv1000 / Nasal / maps / navaids.nas /
Newer Older
518 lines | 18.045kb
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)
127
            .setAlignment('center-bottom-baseline')
128
            .setFont('LiberationFonts/LiberationSans-Regular.ttf')
129
            .setFontSize(24);
130

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

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

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

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

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

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

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

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

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

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

            
255
        me._can.label = me._can.group.createChild('text', me._type ~ '-label_' ~ me._id)
256
            .setDrawMode( canvas.Text.TEXT )
257
            .setTranslation(0,42)
258
            .setAlignment('center-bottom-baseline')
259
            .setFont('LiberationFonts/LiberationSans-Regular.ttf')
260
            .setFontSize(24);
261

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

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

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

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

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

            
345
        m._model = nil;
346
        m.device = device;
347
        m._visibility = m.device.role == 'MFD';
348

            
349
        m._group = group.createChild('map', 'MFD map')
350
            .setTranslation(
351
                m.device.role == 'MFD' ? (m.device.data.mapview[0] + m.device.data.mapclip.left)/2 : 120,
352
                m.device.role == 'MFD' ? 400 : 600)
353
            .setVisible(m._visibility);
354
            m._group._node
355
                .getNode('range', 1).setDoubleValue(m.device.data['range-factor']);
356

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

            
373
        m._mapOptions = {
374
            declutterLand : 3,
375
            declutterNAV  : 3,
376
            lightning     : 0,
377
            reports       : 0,
378
            overlay       : 0,
379
            range         : m.device.data['range-nm'],
380
            rangeLow      : m.device.data['range-nm'] / 2,
381
            runwayLength  : -1,
382
            orientation   : m.device.data.orientation.map,
383
        };
384

            
385
        m._results = nil;
386

            
387
        return m;
388
    },
389
    update : func {
390
        me._group._node.getNode('ref-lat', 1).setDoubleValue(data.lat);
391
        me._group._node.getNode('ref-lon', 1).setDoubleValue(data.lon);
392

            
393
        me._group._node
394
            .getNode('range', 1).setDoubleValue(me.device.data['range-factor']);
395
        me._mapOptions.orientation = me.device.data.orientation.map;
396

            
397
        if (me._visibility == 1) {
398
            me.loadAirport();
399
            foreach (var n; ['VOR', 'TACAN', 'NDB', 'DME'])
400
                me.loadNavaid(n);
401
            me.loadAirplane();
402
        }
403
    },
404
    _onVisibilityChange : func {
405
        me._group.setVisible(me._visibility);
406
    },
407
    setMapOptions : func (mapOptions) {
408
        me._mapOptions = mapOptions;
409
        me.update();
410
    },
411
    updateOrientation : func (value) {
412
        me._mapOptions.orientation = value;
413
        for (var i = 0 ; i < me._cache.airport.index ; i +=1) {
414
            item = me._cache.airport.data[i];
415
            item.update(me._mapOptions);
416
        }
417
    },
418
    setRange : func (range=100) {
419
        me._mapOptions.range = me.device.data['range-nm'];
420
        me._mapOptions.rangeLow = me._mapOptions.range/2;
421
        me.update();
422
    },
423
    setRotation : func (deg) {
424
        me._group.setRotation(deg * D2R);
425
    },
426
    setVisible : func (v) {
427
        if (me._visibility != v) {
428
            me._visibility = v;
429
            me._onVisibilityChange();
430
        }
431
    },
432
    _onVisibilityChange : func {
433
        me._group.setVisible(me._visibility);
434
    },
435
    # positioned.findWithinRange : any, fix, vor, ndb, ils, dme, tacan
436

            
437
    loadAirport : func {
438
        me._cache.airport.index = 0;
439
        var results = positioned.findWithinRange(me._mapOptions.range * 2.5, 'airport');
440
        var item = nil;
441

            
442
        if (me._mapOptions.declutterNAV >= 2)
443
            me._mapOptions.runwayLength = MAP_RUNWAY_AT_RANGE(me._mapOptions.range);
444
        elsif (me._mapOptions.declutterNAV >= 1)
445
            me._mapOptions.runwayLength = 2000;
446
        else
447
            me._mapOptions.runwayLength = 3000;
448

            
449
        if (me._mapOptions.runwayLength >= 0) {
450
            foreach (var apt; results) {
451
                if (me._cache.airport.index >= me._cache.airport.max )
452
                    break;
453

            
454
                if (size(me._cache.airport.data) > me._cache.airport.index)
455
                    item = me._cache.airport.data[me._cache.airport.index];
456
                else {
457
                    item = MapAirportItem.new(me._cache.airport.index);
458
                    item.create(me._can.airport);
459
                    append(me._cache.airport.data, item);
460
                }
461

            
462
                if (item.draw(apt, me._mapOptions)) {
463
                    item.setVisible(1);
464
                    me._cache.airport.index += 1;
465
                }
466
            }
467
        }
468

            
469
        for (var i = me._cache.airport.index ; i < size(me._cache.airport.data) ; i +=1) {
470
            item = me._cache.airport.data[i];
471
            item.setVisible(0);
472
        }
473
    },
474
    loadNavaid : func (type) {
475
        me._cache[type].index = 0;
476
        if (me._mapOptions.declutterNAV >= 3) { # TODO test for DME and NDB range < 100nm
477
            var range = me._mapOptions.range * 2.5;
478
            var txRange = MAP_TXRANGE_VOR(me._mapOptions.range);
479
            var results = positioned.findWithinRange(range, type);
480
            var item = nil;
481
            foreach (var n; results) {
482
                if (n.range_nm < txRange)
483
                    break;
484

            
485
                if (me._cache[type].index >= me._cache[type].max )
486
                    break;
487

            
488
                if (size(me._cache[type].data) > me._cache[type].index) {
489
                    item = me._cache[type].data[me._cache[type].index];
490
                    item.setData(n, type, me._mapOptions);
491
                }
492
                else {
493
                    item = MapNavaidItem.new(me._cache[type].index, type);
494
                    item.create(me._can[type]);
495
                    item.setData(n, type, me._mapOptions);
496
                    append(me._cache[type].data, item);
497
                }
498
                item.setVisible(1);
499
                me._cache[type].index += 1;
500
            }
501
        }
502
        for (var i = me._cache[type].index ; i < size(me._cache[type].data) ; i +=1) {
503
            item = me._cache[type].data[i];
504
            item.setVisible(0);
505
        }
506
    },
507
    loadAirplane : func {
508
        if (!me._cache.airplane.displayed) {
509
            me._cache.airplane.item = MapAirplaneItem.new();
510
            me._cache.airplane.item.create(me._can['airplane']);
511
            me._cache.airplane.displayed = 1;
512
        }
513
        me._cache.airplane.item.setData(me.device.data.orientation.airplane);
514
        me._cache.airplane.item.setVisible(1);
515
    },
516
};
517
# }}}
518