zkv1000 / Nasal / maps / navaids.nas /
Newer Older
517 lines | 17.995kb
separates maps code
Sébastien MARQUE authored on 2017-05-11
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 MapIconCache = {
5
# creates at init an icons cache for navaids, airports and airplane {{{
6
    new : func (svgFile) {
7
        var m = { parents:[MapIconCache] };
8

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

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

            
21
        m._sourceRectMap = {};
22

            
23
        var icons = [ 'airplane' ];
24

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

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

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

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

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

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

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

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

            
123
        me._can.label = me._can.group.createChild('text', 'airport-label_' ~ me._id)
124
            .setDrawMode( canvas.Text.TEXT )
125
            .setTranslation(0, 37)
126
            .setAlignment('center-bottom-baseline')
127
            .setFont('LiberationFonts/LiberationSans-Regular.ttf')
128
            .setFontSize(24);
129

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
384
        m._results = nil;
385

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

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

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

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

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

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

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

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

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

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

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