PageSourceSearch

https://olivierld.github.io/webcomponents/widgets/boatoverview/BoatOverview.js

js olivierld.github.io collected 2026-10-03 09:43:07 UTC 49,491 bytes, 1,731 lines download raw bytes

1/*
2 * HTML Color names: https://www.w3schools.com/colors/colors_names.asp
3 *
4 * Attributes:
5 * Values: width, height, BSP, HDG, AWS, AWA, TWS, TWA, TWD, CDR, CSP, Leeway, CMG, VMG, B2WP, Decl, dev
6 * Booleans: displayCurrent, displayLabels, displayVMG, vmgOnWind or vmgOnWP, with D, d, W
7 * Options: BoatShape (mono, cata, tri, plane), WPName
8 *
9 * Designed to represent the data coming from the NavServer.
10 * NOTE: The consistency of the data is not maintained by the Component!!
11 * Example:
12{
13  "Damping": 30,
14  "HDG Offset": 0,
15  "To Waypoint": "RANGI   ",
16  "CDR": {
17    "angle": 115.39230004857473
18  },
19  "Daily": {
20    "distance": 12.2
21  },
22  "Max Leeway": 10,
23  "VMG on Wind": -3.8584310339481522,
24  "HDG c.": {
25    "angle": 213
26  },
27  "CMG": {
28    "angle": 222.01692220976113
29  },
30  "BSP": {
31    "speed": 7.1
32  },
33  "TWA": {
34    "angle": -130.38922223613739
35  },
36  "TWD": {
37    "angle": 91
38  },
39  "Current calculated with damping": {
40    "30000": {
41      "bufferLength": 30000,
42      "speed": {
43        "speed": 0.32396646536173773
44      },
45      "direction": {
46        "angle": 48.891255868064114
47      },
48      "nbPoints": 11,
49      "oldest": "Sun, 2010 Nov 21 22:07:44 UTC",
50      "latest": "Sun, 2010 Nov 21 22:07:44 UTC",
51      "len": 30000
52    },
53    "60000": {
54      "bufferLength": 60000,
55      "speed": {
56        "speed": 0.32396646536173773
57      },
58      "direction": {
59        "angle": 48.891255868064114
60      },
61      "nbPoints": 11,
62      "oldest": "Sun, 2010 Nov 21 22:07:14 UTC",
63      "latest": "Sun, 2010 Nov 21 22:07:44 UTC",
64      "len": 30000
65    },
66    "600000": {
67      "bufferLength": 600000,
68      "speed": {
69        "speed": 0.32396646536173773
70      },
71      "direction": {
72        "angle": 48.891255868064114
73      },
74      "nbPoints": 11,
75      "oldest": "Sun, 2010 Nov 21 22:07:14 UTC",
76      "latest": "Sun, 2010 Nov 21 22:07:44 UTC",
77      "len": 30000
78    }
79  },
80  "Position": {
81    "lat": -9.108366666666667,
82    "lng": -140.20933333333332
83  },
84  "Log": {
85    "distance": 3013
86  },
87  "Solar Time": {
88    "date": "Nov 21, 2010, 4:46:56 AM",
89    "fmtDate": {
90      "epoch": 0,
91      "year": 0,
92      "month": 0,
93      "day": 0,
94      "hour": 12,
95      "min": 46,
96      "sec": 56
97    }
98  },
99  "BSP Factor": 1,
100  "Set and Drift": {
101    "speed": 0.32,
102    "angle": 49
103  },
104  "From Waypoint": "",
105  "TWS": {
106    "speed": 18.5
107  },
108  "GPS Time": {
109    "date": "Nov 21, 2010, 2:07:47 PM",
110    "fmtDate": {
111      "epoch": 0,
112      "year": 0,
113      "month": 0,
114      "day": 0,
115      "hour": 22,
116      "min": 7,
117      "sec": 47
118    }
119  },
120  "Distance to WP": {
121    "distance": 561.7
122  },
123  "AWS Factor": 1,
124  "Water Temperature": {
125    "temperature": 26.5
126  },
127  "Small Distance": 0.06425312432932662,
128  "NMEA": "$CCVDR,49.0,T,39.0,M,0.32,N*0F\r\n",
129  "NMEA_AS_IS": {
130    "VLW": "$IIVLW,03013,N,012.2,N*53\r",
131    "VHW": "$IIVHW,,,213,M,07.1,N,,*62\r",
132    "VDR": "$CCVDR,49.0,T,39.0,M,0.32,N*0F\r\n",
133    "GLL": "$IIGLL,0906.498,S,14012.558,W,220745,A,A*5D\r",
134    "RMB": "$IIRMB,A,3.00,R,,RANGI   ,,,,,561.70,230,06.7,V,A*02\r",
135    "RMC": "$IIRMC,220747,A,0906.502,S,14012.560,W,06.9,215,211110,10,E,A*00\r",
136    "DPT": "$IIDPT,001.3,+0.7,*42\r",
137    "HDG": "$IIHDG,211,,,10,E*11\r",
138    "MWV": "$CCMWV,230.0,T,018.5,N,A*36\r\n",
139    "VWR": "$IIVWR,109,L,15.8,N,,,,*7B\r",
140    "MTW": "$IIMTW,+26.5,C*39\r",
141    "VWT": "$CCVWT,130.4,L,18.5,N,9.5,M,34.3,K*53\r\n",
142    "MWD": "$CCMWD,091.0,T,081.0,M,18.5,N,9.5,M*75\r\n"
143  },
144  "D": {
145    "angle": 10
146  },
147  "XTE": {
148    "distance": 3
149  },
150  "AWA": {
151    "angle": -109
152  },
153  "Depth": {
154    "depthInMeters": 2
155  },
156  "Bearing to WP": {
157    "angle": 230
158  },
159  "W": {
160    "angle": 9.01692220976113
161  },
162  "Speed to WP": {
163    "speed": 6.7
164  },
165  "COG": {
166    "angle": 215
167  },
168  "AWS": {
169    "speed": 15.8
170  },
171  "HDG true": {
172    "angle": 222.01692220976113
173  },
174  "AWA Offset": 0,
175  "CSP": {
176    "speed": 0.8796925520240182
177  },
178  "d": {
179    "angle": -0.9830777902388692
180  },
181  "Default Declination": {
182    "angle": 14
183  },
184  "Deviation file name": "dp_2011_04_15.csv",
185  "HDG mag.": {
186    "angle": 212.01692220976113
187  },
188  "SOG": {
189    "speed": 6.9
190  },
191  "Leeway": {
192    "angle": 0
193  },
194  "GPS Date & Time": {
195    "date": "Nov 21, 2010, 2:07:47 PM",
196    "epoch": 1290377267000,
197    "fmtDate": {
198      "epoch": 1290377267000,
199      "year": 2010,
200      "month": 11,
201      "day": 21,
202      "hour": 22,
203      "min": 7,
204      "sec": 47
205    }
206  },
207  "WayPoint pos": {
208    "lat": 0,
209    "lng": 0
210  },
211  "VMG to Waypoint": 6.664888201394572,
212  "Steer": "R"
213}
214 *
215 */
216const boatOverviewVerbose = false;
217const BOAT_OVERVIEW_TAG_NAME = 'boat-overview';
218
219const boatOverviewDefaultColorConfig = {
220	displayBackgroundGradient: {
221		from: 'silver',
222		to: 'lightgray'
223	},
224	gridColor: 'gray',
225
226	twArrowColor: 'black',
227	bspArrowColor: 'red',
228	cmgArrowColor:'cyan',
229	awArrowColor: 'blue',
230	gpsWsArrowColor: 'coral',
231	vmgArrowColor: 'red',
232	dDWArrowColor: 'yellow',
233	currentArrowColor: 'royalblue',
234	boatFillColor: 'silver',
235	boatOutlineColor: 'blue',
236
237	nmeaDataDisplayColor: 'royalblue',
238	calculatedDataDisplayColor: 'darkcyan',
239	vmgDataDisplayColor: 'red',
240	dDWDataDisplayColor: 'yellow',
241
242};
243
244import * as Utilities from "../utilities/Utilities.js";
245
246/* global HTMLElement */
247class BoatOverview extends HTMLElement {
248
249	static get observedAttributes() { // That's a big one...
250		return [
251			"width",        // Integer. Canvas width
252			"height",       // Integer. Canvas height
253			"bsp",          // Float. Boat Speed Numeric value, knots
254			"hdg",          // Integer, True Heading [0..360]
255			"aws",          // Float. App Wind Speed Numeric value, knots
256			"awa",          // Integer. App Wind Angle [-180..180] Numeric value
257			"tws",          // Float. True Wind Speed Numeric value, knots
258			"twa",          // Integer. True Wind Angle [-180..180] Numeric value
259			"twd",          // Integer. True Wind Direction [0..360] Numeric value
260			"vmg",          // Float. Velocity Made Good value, knots
261			"sog",          // Float. Speed Over Ground Numeric value, knots
262			"cog",          // Integer. Course Over Ground [0..360] Numeric value
263			"cdr",          // Integer. Current Direction [0..360] Numeric value
264			"csp",          // Float. Current Speed Numeric value, knots
265			"lwy",          // Float. Leeway [-180..180] Numeric value
266			"cmg",          // Integer. Course Made Good [0..360] Numeric value
267			"b2wp",         // Integer. Bearing to Next Way Point [0..360] Numeric value
268			"decl",         // Float. Magnetic Declination Numeric value +/-
269			"dev",          // Float. Magnetic deviation Numeric value +/-
270			"with-current", // Boolean. Draw current
271			"with-gps",    // Boolean. Draw SOG & COG
272			"with-wind",    // Boolean. Draw wind (app & true)
273			"with-true-wind", // Boolean. Draw true wind
274			"with-labels",  // Boolean. Draw Labels on graphic
275			"with-vmg",     // Boolean. Draw VMG
276			"vmg-on-wind",  // Boolean. true: on Wind, false: on WayPoint
277			"with-w",       // Boolean. Draw D & d
278			"boat-shape",   // String. MONO, CATA, TRI, PLANE
279			"zoom-on-boat", // Float, enforce zoom in (>1) or out (<1). Default 1
280			"wp-name"       // String. Next WayPoint name
281
282		];
283	}
284
285	constructor() {
286		super();
287		this._shadowRoot = this.attachShadow({mode: 'open'}); // 'open' means it is accessible from external JavaScript.
288		// create and append a <canvas>
289		this.canvas = document.createElement("canvas");
290		let fallbackElemt = document.createElement("h1");
291		let content = document.createTextNode("This is a Boat Overview Web Component, on an HTML5 canvas");
292		fallbackElemt.appendChild(content);
293		this.canvas.appendChild(fallbackElemt);
294		this.shadowRoot.appendChild(this.canvas);
295
296		// Default values
297		this._width = 600;
298		this._height = 500;
299
300		this._bsp = 0; // Boat Speed
301		this._hdg = 0; // True Heading
302		this._aws = 0; // App Wind Speed
303		this._awa = 0; // App Wind Angle
304		this._tws = 0; // True Wind Speed
305		this._twa = 0; // True Wind Angle
306		this._twd = 0; // True Wind Direction
307		this._vmg = 0; // Velocity Made Good
308		this._cog = 0; // Course Over Ground
309		this._sog = 0; // Speed Over Ground
310		this._cdr = 0; // Current Direction
311		this._csp = 0; // Current Speed
312		this._lwy = 0; // Leeway
313		this._cmg = 0; // Course Made Good
314		this._b2wp = 0; // Bearing to WP
315		this._Decl = 0; // Declination
316		this._dev = 0;  // deviation
317
318		this._zoom = 1.0;
319
320		this._withCurrent = false;
321		this._withLabels = true;
322		this._withGPS = true;
323		this._withWind = true;
324		this._withTrueWind = true;
325		this._withVMG = true;
326		this._vmgOnWind = true; // False means vmg on WP
327		this._withW = false;    // Requires D and d
328
329		this._boatShape = "MONO"; // "CATA"; //"MONO"; // "TRI"; // "PLANE";
330		this._wpName = "";
331
332		this._previousClassName = "";
333		this.boatOverviewColorConfig = boatOverviewDefaultColorConfig;
334
335		this.speedScale = 10; // Default value
336
337		this.WL_RATIO_COEFF = 0.75; // Ratio to apply to (3.5 * Width / Length)
338		this.BOAT_LENGTH = 100; // 50;
339	}
340
341	// Called whenever the custom element is inserted into the DOM.
342	connectedCallback() {
343		if (boatOverviewVerbose) {
344			console.log("connectedCallback invoked.");
345		}
346		this.repaint();
347	}
348
349	// Called whenever the custom element is removed from the DOM.
350	disconnectedCallback() {
351		if (boatOverviewVerbose) {
352			console.log("disconnectedCallback invoked");
353		}
354	}
355
356	// Called whenever an attribute is added, removed or updated.
357	// Only attributes listed in the observedAttributes property are affected.
358	attributeChangedCallback(attrName, oldVal, newVal) {
359		if (boatOverviewVerbose) {
360			console.log("attributeChangedCallback invoked on " + attrName + " from " + oldVal + " to " + newVal);
361		}
362		switch (attrName) {
363			case "width":
364				this._width = parseInt(newVal);
365				break;
366			case "height":
367				this._height = parseInt(newVal);
368				break;
369			case "bsp":
370				this._bsp = parseFloat(newVal);
371				break;
372			case "hdg":
373				this._hdg = parseInt(newVal);
374				break;
375			case "aws":
376				this._aws = parseFloat(newVal);
377				break;
378			case "awa":
379				this._awa = parseInt(newVal);
380				break;
381			case "tws":
382				this._tws = parseFloat(newVal);
383				break;
384			case "twa":
385				this._twa = parseInt(newVal);
386				break;
387			case "twd":
388				this._twd = parseInt(newVal);
389				break;
390			case "vmg":
391				this._vmg = parseFloat(newVal);
392				break;
393			case "sog":
394				this._sog = parseFloat(newVal);
395				break;
396			case "cog":
397				this._cog = parseInt(newVal);
398				break;
399			case "cdr":
400				this._cdr = parseInt(newVal);
401				break;
402			case "csp":
403				this._csp = parseFloat(newVal);
404				break;
405			case "lwy":
406				this._lwy = parseFloat(newVal);
407				break;
408			case "cmg":
409				this._cmg = parseInt(newVal);
410				break;
411			case "b2wp":
412				this._b2wp = parseFloat(newVal);
413				break;
414			case "decl":
415				this._Decl = parseFloat(newVal);
416				break;
417			case "dev":
418				this._dev = parseFloat(newVal);
419				break;
420
421			case "zoom-on-boat":
422				this._zoom = parseFloat(newVal);
423				break;
424
425			case "with-current":
426				this._withCurrent = (newVal === 'true');
427				break;
428			case "with-gps":
429				this._withGPS = (newVal === 'true');
430				break;
431			case "with-true-wind":
432				this._withTrueWind = (newVal === 'true');
433				break;
434			case "with-wind":
435				this._withWind = (newVal === 'true');
436				break;
437			case "with-labels":
438				this._withLabels = (newVal === 'true');
439				break;
440			case "with-vmg":
441				this._withVMG = (newVal === 'true');
442				break;
443			case "vmg-on-wind":
444				this._vmgOnWind = (newVal === 'true');
445				break;
446			case "with-w":
447				this._withW = (newVal === 'true');
448				break;
449
450			case "boat-shape":
451				console.log('BoatShape changing');
452				if (newVal === 'MONO' || newVal === 'CATA' || newVal === 'TRI' || newVal === 'PLANE') {
453					this._boatShape = newVal;
454				}
455				break;
456			case "wp-name":
457				this._wpName = newVal;
458				break;
459			default:
460				break;
461		}
462		this.repaint();
463	}
464
465	// Called whenever the custom element has been moved into a new document.
466	adoptedCallback() {
467		if (boatOverviewVerbose) {
468			console.log("adoptedCallback invoked");
469		}
470	}
471
472	// Setters
473	set width(val) {
474		this.setAttribute("width", val);
475	}
476
477	set height(val) {
478		this.setAttribute("height", val);
479	}
480
481	set bsp(val) {
482		this.setAttribute("bsp", val);
483	}
484
485	set hdg(val) {
486		this.setAttribute("hdg", val);
487	}
488
489	set aws(val) {
490		this.setAttribute("aws", val);
491	}
492
493	set awa(val) {
494		this.setAttribute("awa", val);
495	}
496
497	set tws(val) {
498		this.setAttribute("tws", val);
499	}
500
501	set twa(val) {
502		this.setAttribute("twa", val);
503	}
504
505	set twd(val) {
506		this.setAttribute("twd", val);
507	}
508
509	set vmg(val) {
510		this.setAttribute("vmg", val);
511	}
512
513	set cog(val) {
514		this.setAttribute("cog", val);
515	}
516
517	set sog(val) {
518		this.setAttribute("sog", val);
519	}
520
521	set cdr(val) {
522		this.setAttribute("cdr", val);
523	}
524
525	set csp(val) {
526		this.setAttribute("csp", val);
527	}
528
529	set lwy(val) {
530		this.setAttribute("lwy", val);
531	}
532
533	set cmg(val) {
534		this.setAttribute("cmg", val);
535	}
536
537	set b2wp(val) {
538		this.setAttribute("b2wp", val);
539	}
540
541	set Decl(val) {
542		this.setAttribute("decl", val);
543	}
544
545	set dev(val) {
546		this.setAttribute("dev", val);
547	}
548
549	set zoomOnBoat(val) {
550		this.setAttribute("zoom-on-boat", val);
551	}
552
553	set withGPS(val) {
554		this.setAttribute("with-gps", val);
555	}
556
557	set withCurrent(val) {
558		this.setAttribute("with-current", val);
559	}
560
561	set withTrueWind(val) {
562		this.setAttribute("with-true-wind", val);
563	}
564
565	set withWind(val) {
566		this.setAttribute("with-wind", val);
567	}
568
569	set withLabels(val) {
570		this.setAttribute("with-labels", val);
571	}
572
573	set withVMG(val) {
574		this.setAttribute("with-vmg", val);
575	}
576
577	set vmgOnWind(val) {
578		this.setAttribute("vmg-on-wind", val);
579	}
580
581	set withW(val) {
582		this.setAttribute("with-w", val);
583	}
584
585	set wpName(val) {
586		this.setAttribute("wp-name", val);
587	}
588
589	set boatShape(val) {
590		this.setAttribute("boat-shape", val);
591	}
592
593	set shadowRoot(val) {
594		this._shadowRoot = val;
595	}
596
597	// Getters
598	get width() {
599		return this._width;
600	}
601
602	get height() {
603		return this._height;
604	}
605
606	get bsp() {
607		return this._bsp;
608	}
609
610	get hdg() {
611		return this._hdg;
612	}
613
614	get aws() {
615		return this._aws;
616	}
617
618	get awa() {
619		return this._awa;
620	}
621
622	get twa() {
623		return this._twa;
624	}
625
626	get tws() {
627		return this._tws;
628	}
629
630	get twd() {
631		return this._twd;
632	}
633
634	get vmg() {
635		return this._vmg;
636	}
637
638	get cog() {
639		return this._cog;
640	}
641
642	get sog() {
643		return this._sog;
644	}
645
646	get cdr() {
647		return this._cdr;
648	}
649
650	get csp() {
651		return this._csp;
652	}
653
654	get lwy() {
655		return this._lwy;
656	}
657
658	get cmg() {
659		return this._cmg;
660	}
661
662	get b2wp() {
663		return this._b2wp;
664	}
665
666	get Decl() {
667		return this._Decl;
668	}
669
670	get dev() {
671		return this._dev;
672	}
673
674	get zoomOnBoat() {
675		return this._zoom;
676	}
677
678	get withGPS() {
679		return this._withGPS;
680	}
681
682	get withCurrent() {
683		return this._withCurrent;
684	}
685
686	get withTrueWind() {
687		return this._withTrueWind;
688	}
689
690	get withWind() {
691		return this._withWind;
692	}
693
694	get withLabels() {
695		return this._withLabels;
696	}
697
698	get withVMG() {
699		return this._withVMG;
700	}
701
702	get vmgOnWind() {
703		return this._vmgOnWind;
704	}
705
706	get withW() {
707		return this._withW;
708	}
709
710	get wpName() {
711		return this._wpName;
712	}
713
714	get boatShape() {
715		return this._boatShape;
716	}
717
718	get shadowRoot() {
719		return this._shadowRoot;
720	}
721
722	// Component methods
723	getColorConfig(classNames) {
724		let colorConfig = boatOverviewDefaultColorConfig;
725		let classes = classNames.split(" ");
726		for (let cls = 0; cls < classes.length; cls++) {
727			let cssClassName = classes[cls];
728			for (let s = 0; s < document.styleSheets.length; s++) {
729				// console.log("Walking though ", document.styleSheets[s]);
730				try {
731					for (let r = 0; document.styleSheets[s].cssRules !== null && r < document.styleSheets[s].cssRules.length; r++) {
732						let selector = document.styleSheets[s].cssRules[r].selectorText;
733						//			console.log(">>> ", selector);
734						if (selector !== undefined && (selector === '.' + cssClassName || (selector.indexOf('.' + cssClassName) > -1 && selector.indexOf(BOAT_OVERVIEW_TAG_NAME) > -1))) { // Cases like "tag-name .className"
735							//				console.log("  >>> Found it! [%s]", selector);
736							let cssText = document.styleSheets[s].cssRules[r].style.cssText;
737							let cssTextElems = cssText.split(";");
738							cssTextElems.forEach((elem) => {
739								if (elem.trim().length > 0) {
740									let keyValPair = elem.split(":");
741									let key = keyValPair[0].trim();
742									let value = keyValPair[1].trim();
743									switch (key) {
744										case '--display-background-gradient-from':
745											colorConfig.displayBackgroundGradient.from = value;
746											break;
747										case '--display-background-gradient-to':
748											colorConfig.displayBackgroundGradient.to = value;
749											break;
750										case '--grid-color':
751											colorConfig.gridColor = value;
752											break;
753										case '--tw-arrow-color':
754											colorConfig.twArrowColor = value;
755											break;
756										case '--bsp-arrow-color':
757											colorConfig.bspArrowColor = value;
758											break;
759										case '--cmg-arrow-color':
760											colorConfig.cmgArrowColor = value;
761											break;
762										case '--aw-arrow-color':
763											colorConfig.awArrowColor = value;
764											break;
765										case '--gps-ws-arrow-color':
766											colorConfig.gpsWsArrowColor = value;
767											break;
768										case '--vmg-arrow-color':
769											colorConfig.vmgArrowColor = value;
770											break;
771										case '--variation-arrow-color':
772											colorConfig.dDWArrowColor = value;
773											break;
774										case '--current-arrow-color':
775											colorConfig.currentArrowColor = value;
776											break;
777										case '--boat-fill-color':
778											colorConfig.boatFillColor = value;
779											break;
780										case '--boat-outline-color':
781											colorConfig.boatOutlineColor = value;
782											break;
783										case '--nmea-display-color':
784											colorConfig.nmeaDataDisplayColor = value;
785											break;
786										case '--calculated-color':
787											colorConfig.calculatedDataDisplayColor = value;
788											break;
789										case '--variation-display-color':
790											colorConfig.dDWDataDisplayColor = value;
791											break;
792										case '--vmg-display-color':
793											colorConfig.vmgDataDisplayColor = value;
794											break;
795										default:
796											break;
797									}
798								}
799							});
800						}
801					}
802				} catch (err) {
803					// Absorb
804				}
805			}
806		}
807		return colorConfig;
808	}
809
810	repaint() {
811		this.drawBoatOverview();
812	}
813
814	getCanvasCenter() {
815		let cw = this._width;
816		let ch = this._height;
817		let distFromRight = Math.min(cw, ch) / 2;
818
819		return { x: cw - distFromRight, y: ch / 2};
820	}
821
822	drawTrueWind(context) {
823		let cWidth  = this._width;
824		let cHeight = this._height;
825
826		let _twd = Math.toRadians(this._twd);
827		context.beginPath();
828		let center = this.getCanvasCenter();
829		let x = center.x;
830		let y = center.y;
831
832		let windLength = this._zoom * this._tws * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
833		let dX = windLength * Math.sin(_twd);
834		let dY = - windLength * Math.cos(_twd);
835		// create a new line object
836		let line = new Line(x + dX, y + dY, x, y);
837		// draw the line
838		context.strokeStyle = this.boatOverviewColorConfig.twArrowColor;
839		context.fillStyle   = this.boatOverviewColorConfig.twArrowColor;
840		context.lineWidth = 5;
841		line.drawWithAnemoArrowheads(context);
842		context.closePath();
843		if (this._withLabels) {
844			context.font= "bold 12px Arial";
845			context.fillStyle = this.boatOverviewColorConfig.twArrowColor;
846			context.fillText("TWS:" + this._tws.toFixed(2) + " kts", x + dX, y + dY);
847			context.fillText("TWA:" + this._twa + "°", x + dX, y + dY + 14);
848		}
849	}
850
851	drawAppWind(context) {
852		let cWidth  = this._width;
853		let cHeight = this._height;
854
855		let wd = this._hdg + this._awa; // Direction the wind is blowing TO
856		while (wd > 360) {
857			wd -= 360;
858		}
859		let _awd = Math.toRadians(wd);
860		context.beginPath();
861		let center = this.getCanvasCenter();
862		let x = center.x;
863		let y = center.y;
864
865		let windLength = this._zoom * this._aws * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
866		let dX = windLength * Math.sin(_awd);
867		let dY = - windLength * Math.cos(_awd);
868		// create a new line object
869		let line = new Line(x + dX, y + dY, x, y);
870		// draw the line
871		context.strokeStyle = this.boatOverviewColorConfig.awArrowColor;
872		context.fillStyle   = this.boatOverviewColorConfig.awArrowColor;
873		context.lineWidth = 5;
874		line.drawWithAnemoArrowheads(context);
875		context.closePath();
876		if (this._withLabels) {
877			context.font= "bold 12px Arial";
878			context.fillStyle = this.boatOverviewColorConfig.awArrowColor;
879			context.fillText("AWS:" + this._aws + " kts", x + dX, y + dY);
880			context.fillText("AWA:" + this._awa + "°", x + dX, y + dY + 14);
881		}
882	}
883
884	drawBSP(context) {
885		if (this._bsp === 0) {
886			return;
887		}
888
889		let cWidth  = this._width;
890		let cHeight = this._height;
891
892		let _hdg = Math.toRadians(this._hdg);
893		context.beginPath();
894		let center = this.getCanvasCenter();
895		let x = center.x;
896		let y = center.y;
897
898		let bspLength = this._zoom * this._bsp * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
899		let dX = bspLength * Math.sin(_hdg);
900		let dY = - bspLength * Math.cos(_hdg);
901		// create a new line object
902		let line = new Line(x, y, x + dX, y + dY);
903		// draw the line
904		context.strokeStyle = this.boatOverviewColorConfig.bspArrowColor;
905		context.lineWidth = 3;
906		line.drawHollowArrow(context);
907		context.closePath();
908//    let metrics = context.measureText(valueToDisplay);
909//    len = metrics.width;
910		if (this._withLabels) {
911			context.font= "bold 12px Arial";
912			context.fillStyle = this.boatOverviewColorConfig.bspArrowColor;
913			context.fillText("BSP:" + this._bsp.toFixed(2) + " kts", x + dX, y + dY);
914			context.fillText("HDG:" + this._hdg.toFixed(0) + "°", x + dX, y + dY + 14);
915		}
916	}
917
918	drawCMG(context) {
919		if (this._bsp === 0 || this._lwy === 0) {
920			return;
921		}
922
923		let cWidth  = this._width;
924		let cHeight = this._height;
925
926		let _hdg = Math.toRadians(this._cmg);
927		context.beginPath();
928		let center = this.getCanvasCenter();
929		let x = center.x;
930		let y = center.y;
931
932		let bspLength = this._zoom * this._bsp * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
933		let dX = bspLength * Math.sin(_hdg);
934		let dY = - bspLength * Math.cos(_hdg);
935		// create a new line object
936		let line = new Line(x, y, x + dX, y + dY);
937		// draw the line
938		context.strokeStyle = this.boatOverviewColorConfig.cmgArrowColor;
939		context.fillStyle   = this.boatOverviewColorConfig.cmgArrowColor;
940		context.lineWidth = 5;
941		line.drawWithArrowhead(context);
942		context.closePath();
943		if (this._withLabels) {
944			context.font= "bold 12px Arial";
945			context.fillStyle = this.boatOverviewColorConfig.calculatedDataDisplayColor;
946			context.fillText("CMG:" + this._cmg.toFixed(0) + "°", x + dX, y + dY);
947		}
948	}
949
950	drawNorths(context) {
951		if (this._bsp === 0 || (this._Decl === 0 && this._dev === 0)) {
952			return;
953		}
954
955		let cWidth  = this._width;
956		let cHeight = this._height;
957		// Warning: Represent the Norths, not the headings!!!
958		let magNorth = this._Decl;
959		let compassNorth = magNorth + this._dev;
960
961		while (magNorth < 0) {
962			magNorth += 360;
963		}
964		while (compassNorth < 0) {
965			compassNorth += 360;
966		}
967
968		let _magNorth = Math.toRadians(magNorth);
969		let center = this.getCanvasCenter();
970		let x = center.x;
971		let y = center.y;
972
973		let bspLength = this._zoom * this._bsp * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
974		// True North first
975		let line = new Line(x, y, x, y - (bspLength * 1.1));
976		context.beginPath();
977		context.strokeStyle = this.boatOverviewColorConfig.dDWArrowColor;
978		context.fillStyle   = this.boatOverviewColorConfig.dDWArrowColor;
979		context.lineWidth = 1;
980		line.drawWithArrowhead(context);
981		context.closePath();
982		if (this._withLabels) {
983			context.font= "12px Arial";
984			context.fillStyle = this.boatOverviewColorConfig.dDWDataDisplayColor;
985			context.fillText("N", x, y  - (bspLength * 1.1));
986		}
987
988		context.beginPath();
989		let dX = bspLength * Math.sin(_magNorth);
990		let dY = - bspLength * Math.cos(_magNorth);
991		// create a new line object
992		line = new Line(x, y, x + dX, y + dY);
993		// draw the line
994		context.lineWidth = 5;
995		line.drawWithArrowhead(context);
996		context.closePath();
997		if (this._withLabels) {
998			context.font= "bold 12px Arial";
999			context.fillStyle = this.boatOverviewColorConfig.dDWDataDisplayColor;
1000			context.fillText("Mag N:" + magNorth.toFixed(0) + "°", x + dX, y + dY);
1001		}
1002		let _compassNorth = Math.toRadians(compassNorth);
1003		context.beginPath();
1004
1005		dX = bspLength * 0.8 * Math.sin(_compassNorth);
1006		dY = - bspLength * 0.8 * Math.cos(_compassNorth);
1007		// create a new line object
1008		line = new Line(x, y, x + dX, y + dY);
1009		// draw the line
1010		context.strokeStyle = this.boatOverviewColorConfig.dDWArrowColor;
1011		context.fillStyle   = this.boatOverviewColorConfig.dDWArrowColor;
1012		context.lineWidth = 5;
1013		line.drawWithArrowhead(context);
1014		context.closePath();
1015		if (this._withLabels) {
1016			context.font= "bold 12px Arial";
1017			context.fillStyle = this.boatOverviewColorConfig.dDWDataDisplayColor;
1018			context.fillText("Compass N:" + compassNorth.toFixed(0) + "°", x + dX, y + dY);
1019		}
1020	}
1021	drawSOG(context) {
1022		if (this._sog === 0) {
1023			return;
1024		}
1025
1026		let cWidth  = this._width;
1027		let cHeight = this._height;
1028
1029		let _hdg = Math.toRadians(this._cog);
1030		context.beginPath();
1031		let center = this.getCanvasCenter();
1032		let x = center.x;
1033		let y = center.y;
1034
1035		let bspLength = this._zoom * this._sog * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1036		let dX = bspLength * Math.sin(_hdg);
1037		let dY = - bspLength * Math.cos(_hdg);
1038		// create a new line object
1039		let line = new Line(x, y, x + dX, y + dY);
1040		// draw the line
1041		context.strokeStyle = this.boatOverviewColorConfig.gpsWsArrowColor;
1042		context.fillStyle   = this.boatOverviewColorConfig.gpsWsArrowColor;
1043		context.lineWidth = 5;
1044		line.drawWithArrowhead(context);
1045		context.closePath();
1046		if (this._withLabels) {
1047			context.font= "bold 12px Arial";
1048			context.fillStyle = this.boatOverviewColorConfig.gpsWsArrowColor;
1049			context.fillText("SOG:" + this._sog + " kts", x + dX, y + dY);
1050			context.fillText("COG:" + this._cog + "°", x + dX, y + dY + 14);
1051			context.lineWidth = 1;
1052		}
1053	}
1054
1055	drawVMG(context) {
1056		let cWidth = this._width;
1057		let cHeight = this._height;
1058
1059		let _hdg = 0;
1060		context.beginPath();
1061		let center = this.getCanvasCenter();
1062		let x = center.x;
1063		let y = center.y;
1064
1065		if (this.vmgOnWind) {
1066			_hdg = Math.toRadians(this._twd);
1067		} else {
1068			_hdg = Math.toRadians(this._b2wp);
1069			// Display WP direction
1070			context.strokeStyle = this.boatOverviewColorConfig.vmgArrowColor;
1071			context.fillStyle   = this.boatOverviewColorConfig.vmgArrowColor;
1072			context.lineWidth = 1;
1073			let len = 0.75 * Math.min(cHeight, cWidth) / 2;
1074			let _dX = len * Math.sin(_hdg);
1075			let _dY = - len * Math.cos(_hdg);
1076			let wpLine = new Line(x, y, x + _dX, y + _dY);
1077			wpLine.drawWithArrowhead(context);
1078			context.fillText(this._wpName, x + _dX, y + _dY);
1079		}
1080
1081		let bspLength = this._zoom * this._vmg * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1082		let dX = bspLength * Math.sin(_hdg);
1083		let dY = - bspLength * Math.cos(_hdg);
1084		// create a new line object
1085		let line = new Line(x, y, x + dX, y + dY);
1086		// draw the line
1087		context.strokeStyle = this.boatOverviewColorConfig.vmgArrowColor;
1088		context.fillStyle   = this.boatOverviewColorConfig.vmgArrowColor;
1089		context.lineWidth = 5;
1090		line.drawWithArrowhead(context);
1091		context.closePath();
1092		if (this._withLabels) {
1093			context.save();
1094			context.font= "bold 12px Arial";
1095			context.fillStyle = this.boatOverviewColorConfig.vmgArrowColor;
1096			context.fillText("VMG:" + this._vmg.toFixed(2) + " kts", x + dX, y + dY);
1097			context.restore();
1098		}
1099		if (context.setLineDash !== undefined) {
1100			context.setLineDash([5]);
1101			context.moveTo(x + dX, y + dY);
1102			let _cog = Math.toRadians(this._cog);
1103			let sogLength = this._zoom * this._sog * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1104			dX = sogLength * Math.sin(_cog);
1105			dY = - sogLength * Math.cos(_cog);
1106			context.lineTo(x + dX, y + dY);
1107			context.lineWidth = 1;
1108			context.stroke();
1109			// Reset
1110			context.setLineDash([]);
1111		}
1112	}
1113
1114	drawCurrent(context) {
1115		if (this._csp === 0) {
1116			return;
1117		}
1118
1119		let cWidth = this._width;
1120		let cHeight = this._height;
1121
1122		let center = this.getCanvasCenter();
1123		let x = center.x;
1124		let y = center.y;
1125
1126		let _cmg = Math.toRadians(this._cmg);
1127		let bspLength = this._zoom * this._bsp * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1128		let dXcmg = bspLength * Math.sin(_cmg);
1129		let dYcmg = - bspLength * Math.cos(_cmg);
1130
1131		let _cog = Math.toRadians(this._cog);
1132		let sogLength = this._zoom * this._sog * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1133		let dXcog = sogLength * Math.sin(_cog);
1134		let dYcog = - sogLength * Math.cos(_cog);
1135
1136		context.beginPath();
1137		// create a new line object
1138		let line = new Line(x + dXcmg, y + dYcmg, x + dXcog, y + dYcog);
1139		// draw the line
1140		context.strokeStyle = this.boatOverviewColorConfig.currentArrowColor;
1141		context.fillStyle   = this.boatOverviewColorConfig.currentArrowColor;
1142		context.lineWidth = 5;
1143		line.drawWithArrowhead(context);
1144		context.closePath();
1145		if (this._withLabels) {
1146			context.font= "bold 12px Arial";
1147			context.fillStyle = this.boatOverviewColorConfig.currentArrowColor;
1148			context.fillText("CSP:" + this._csp.toFixed(2) + " kts", x + dXcog, y + dYcog + 28); // + 14 not to overlap the SOG/COG
1149			context.fillText("CDR:" + this._cdr.toFixed(0) + "°", x + dXcog, y + dYcog + 42);
1150		}
1151	}
1152
1153	drawVW(context) { // Velocity Wind
1154		if (this._sog === 0) {
1155			return;
1156		}
1157
1158		let cWidth = this._width;
1159		let cHeight = this._height;
1160
1161		let center = this.getCanvasCenter();
1162		let x = center.x;
1163		let y = center.y;
1164
1165		let wd = this._hdg + this._awa; // Direction the wind is blowing TO
1166		while (wd > 360) {
1167			wd -= 360;
1168		}
1169		let _awd = Math.toRadians(wd);
1170		context.beginPath();
1171		let awLength = this._zoom * this._aws * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1172		let dXaw = awLength * Math.sin(_awd);
1173		let dYaw = - awLength * Math.cos(_awd);
1174
1175		let _twd = Math.toRadians(this._twd);
1176		let twLength = this._zoom * this._tws * ((Math.min(cHeight, cWidth) / 2) / this.speedScale);
1177		let dXtw = twLength * Math.sin(_twd);
1178		let dYtw = - twLength * Math.cos(_twd);
1179
1180		context.beginPath();
1181		// create a new line object
1182		let line = new Line(x + dXaw, y + dYaw, x + dXtw, y + dYtw);
1183		// draw the line
1184		context.strokeStyle = this.boatOverviewColorConfig.gpsWsArrowColor;
1185		context.fillStyle   = this.boatOverviewColorConfig.gpsWsArrowColor;
1186		context.lineWidth = 5;
1187		line.drawWithAnemoArrowheads(context);
1188		context.closePath();
1189	}
1190
1191	drawBoat(context, trueHeading) {
1192		let x = [];
1193		let y = [];// Half, length
1194
1195		let boatLength = this.BOAT_LENGTH * this._zoom;
1196
1197		if (this._boatShape === 'MONO') {
1198			// Width
1199			x.push(this.WL_RATIO_COEFF * 0); // Bow
1200			//     Starboard
1201			x.push(this.WL_RATIO_COEFF * (   1 * boatLength) / 7);
1202			x.push(this.WL_RATIO_COEFF * (   2 * boatLength) / 7);
1203			x.push(this.WL_RATIO_COEFF * (   2 * boatLength) / 7);
1204			x.push(this.WL_RATIO_COEFF * ( 1.5 * boatLength) / 7); // Transom, starboard
1205			//     Port
1206			x.push(this.WL_RATIO_COEFF * (-1.5 * boatLength) / 7); // Transom, port
1207			x.push(this.WL_RATIO_COEFF * (  -2 * boatLength) / 7);
1208			x.push(this.WL_RATIO_COEFF * (  -2 * boatLength) / 7);
1209			x.push(this.WL_RATIO_COEFF * (  -1 * boatLength) / 7);
1210
1211			// Length
1212			y.push((-4 * boatLength) / 7); // Bow
1213			//      Starboard
1214			y.push((-3 * boatLength) / 7);
1215			y.push((-1 * boatLength) / 7);
1216			y.push( (1 * boatLength) / 7);
1217			y.push( (3 * boatLength) / 7);
1218			//     Port
1219			y.push( (3 * boatLength) / 7);
1220			y.push( (1 * boatLength) / 7);
1221			y.push((-1 * boatLength) / 7);
1222			y.push((-3 * boatLength) / 7);
1223
1224		} else if (this._boatShape === 'CATA') {
1225			x.push(this.WL_RATIO_COEFF * 0); // Arm, front, center
1226			// Starboard
1227			x.push(this.WL_RATIO_COEFF * (   1 * boatLength) / 7); // Arm starboard, hull side
1228			x.push(this.WL_RATIO_COEFF * ( 1.5 * boatLength) / 7); // Starboard bow
1229			x.push(this.WL_RATIO_COEFF * (   2 * boatLength) / 7);
1230			x.push(this.WL_RATIO_COEFF * (   2 * boatLength) / 7);
1231			x.push(this.WL_RATIO_COEFF * (   2 * boatLength) / 7);
1232			x.push(this.WL_RATIO_COEFF * ( 1.8 * boatLength) / 7); // Starboard transform, ext
1233			x.push(this.WL_RATIO_COEFF * ( 1.2 * boatLength) / 7); // Starboard transform, int
1234			x.push(this.WL_RATIO_COEFF * (   1 * boatLength) / 7); // Arm, back, starboard, hull side
1235			x.push(this.WL_RATIO_COEFF * (   0 * boatLength) / 7); // Arm, back, starboard, center
1236			// Port
1237			x.push(this.WL_RATIO_COEFF * (  -0 * boatLength) / 7);
1238			x.push(this.WL_RATIO_COEFF * (  -1 * boatLength) / 7);
1239			x.push(this.WL_RATIO_COEFF * (-1.2 * boatLength) / 7);
1240			x.push(this.WL_RATIO_COEFF * (-1.8 * boatLength) / 7);
1241			x.push(this.WL_RATIO_COEFF * (  -2 * boatLength) / 7);
1242			x.push(this.WL_RATIO_COEFF * (  -2 * boatLength) / 7);
1243			x.push(this.WL_RATIO_COEFF * (  -2 * boatLength) / 7);
1244			x.push(this.WL_RATIO_COEFF * (-1.5 * boatLength) / 7);
1245			x.push(this.WL_RATIO_COEFF * (  -1 * boatLength) / 7);
1246
1247			// Length
1248			y.push((-1 * boatLength) / 7);
1249			//   Starboard
1250			y.push((-1 * boatLength) / 7);
1251			y.push((-4 * boatLength) / 7); // Bow
1252			y.push((-1 * boatLength) / 7);
1253			y.push((0 * boatLength) / 7);
1254			y.push((1 * boatLength) / 7);
1255			y.push((3 * boatLength) / 7);
1256			y.push((3 * boatLength) / 7);
1257			y.push((1 * boatLength) / 7);
1258			y.push((1 * boatLength) / 7);
1259			//    Port
1260			y.push((1 * boatLength) / 7);
1261			y.push((1 * boatLength) / 7); // Bow
1262			y.push((3 * boatLength) / 7);
1263			y.push((3 * boatLength) / 7);
1264			y.push((1 * boatLength) / 7);
1265			y.push((0 * boatLength) / 7);
1266			y.push((-1 * boatLength) / 7);
1267			y.push((-4 * boatLength) / 7);
1268			y.push((-1 * boatLength) / 7);
1269
1270		} else if (this._boatShape === 'TRI') {
1271			// Width
1272			x.push(this.WL_RATIO_COEFF * 0); // Bow, center hull
1273			// Starboard
1274			x.push(this.WL_RATIO_COEFF * (  0.3 * boatLength) / 7);
1275			x.push(this.WL_RATIO_COEFF * (  0.6 * boatLength) / 7); // Arm, front, starboard, inside
1276			x.push(this.WL_RATIO_COEFF * (  1.6 * boatLength) / 7); // Arm, front, starboard, outside
1277			x.push(this.WL_RATIO_COEFF * (  1.8 * boatLength) / 7); // Outrigger bow
1278			x.push(this.WL_RATIO_COEFF * (    2 * boatLength) / 7);
1279			x.push(this.WL_RATIO_COEFF * (    2 * boatLength) / 7);
1280			x.push(this.WL_RATIO_COEFF * (  1.9 * boatLength) / 7); // Outrigger transom, ext
1281			x.push(this.WL_RATIO_COEFF * (  1.7 * boatLength) / 7); // Outrigger transom, int
1282			x.push(this.WL_RATIO_COEFF * (  1.6 * boatLength) / 7); // Arm, back, starboard, outside
1283			x.push(this.WL_RATIO_COEFF * (  0.6 * boatLength) / 7); // Arm, back, starboard, inside
1284			x.push(this.WL_RATIO_COEFF * (  0.3 * boatLength) / 7); // Main hull, transom starboard,
1285			// Port
1286			x.push(this.WL_RATIO_COEFF * ( -0.3 * boatLength) / 7);
1287			x.push(this.WL_RATIO_COEFF * ( -0.6 * boatLength) / 7);
1288			x.push(this.WL_RATIO_COEFF * ( -1.6 * boatLength) / 7);
1289			x.push(this.WL_RATIO_COEFF * ( -1.7 * boatLength) / 7); // Outrigger transom, int
1290			x.push(this.WL_RATIO_COEFF * ( -1.9 * boatLength) / 7); // Outrigger transom, ext
1291			x.push(this.WL_RATIO_COEFF * (   -2 * boatLength) / 7);
1292			x.push(this.WL_RATIO_COEFF * (   -2 * boatLength) / 7);
1293			x.push(this.WL_RATIO_COEFF * ( -1.8 * boatLength) / 7); // Outrigger bow
1294			x.push(this.WL_RATIO_COEFF * ( -1.6 * boatLength) / 7); // Arm, front, starboard, outside
1295			x.push(this.WL_RATIO_COEFF * ( -0.6 * boatLength) / 7); // Arm, front, starboard, inside
1296			x.push(this.WL_RATIO_COEFF * ( -0.3 * boatLength) / 7);
1297
1298			// Length
1299			y.push((-4 * boatLength) / 7); // Bow
1300			// Starboard
1301			y.push((-3 * boatLength) / 7);
1302			y.push((-1 * boatLength) / 7); // Starboard arm, front
1303			y.push((-1 * boatLength) / 7); // Starboard arm, front, outrigger
1304			y.push((-2.6 * boatLength) / 7); // Starboard outrigger bow
1305			y.push((-1.5 * boatLength) / 7);
1306			y.push(( 1.5 * boatLength) / 7);
1307			y.push(( 2.5 * boatLength) / 7); // Starboard transom, ext
1308			y.push(( 2.5 * boatLength) / 7); // Starboard transom, ext
1309			y.push(( 1 * boatLength) / 7); // Starboard arm, back, outrigger
1310			y.push(( 1 * boatLength) / 7); // Starboard arm, hull
1311			y.push(( 3 * boatLength) / 7);
1312			// Port
1313			y.push(( 3 * boatLength) / 7);
1314			y.push(( 1 * boatLength) / 7);
1315			y.push(( 1 * boatLength) / 7);
1316			y.push(( 2.5 * boatLength) / 7);
1317			y.push(( 2.5 * boatLength) / 7);
1318			y.push(( 1.5 * boatLength) / 7);
1319			y.push((-1.5 * boatLength) / 7);
1320			y.push((-2.6 * boatLength) / 7);
1321			y.push((-1 * boatLength) / 7);
1322			y.push((-1 * boatLength) / 7);
1323			y.push((-3 * boatLength) / 7);
1324		} else if (this._boatShape === 'PLANE') {
1325			// Width
1326			x.push(this.WL_RATIO_COEFF * 0); // Nose
1327			// Starboard
1328			x.push(this.WL_RATIO_COEFF * (  0.3 * boatLength) / 7);
1329			x.push(this.WL_RATIO_COEFF * (  0.6 * boatLength) / 7); // Wing, front, starboard, inside
1330			x.push(this.WL_RATIO_COEFF * (  4 * boatLength) / 7); // Wing, front, starboard, outside
1331			x.push(this.WL_RATIO_COEFF * (  4 * boatLength) / 7); // Wing, outside, back
1332			x.push(this.WL_RATIO_COEFF * (  0.6 * boatLength) / 7); // Wing, back, starboard, inside
1333			x.push(this.WL_RATIO_COEFF * (  0.3 * boatLength) / 7); // Main hull, transom starboard,
1334			x.push(this.WL_RATIO_COEFF * (    2 * boatLength) / 7); // Main hull, back wing, front
1335			x.push(this.WL_RATIO_COEFF * (    2 * boatLength) / 7); // Main hull, back wing, back, ext
1336			x.push(this.WL_RATIO_COEFF * (  0.1 * boatLength) / 7); // Main hull, back wing, back, int
1337			// Port
1338			x.push(this.WL_RATIO_COEFF * ( -0.1 * boatLength) / 7); // Main hull, back wing, back, int
1339			x.push(this.WL_RATIO_COEFF * (   -2 * boatLength) / 7); // Main hull, back wing, back, ext
1340			x.push(this.WL_RATIO_COEFF * (   -2 * boatLength) / 7); // Main hull, back wing, front
1341			x.push(this.WL_RATIO_COEFF * ( -0.3 * boatLength) / 7); // Main hull, transom starboard,
1342			x.push(this.WL_RATIO_COEFF * ( -0.6 * boatLength) / 7); // Wing, back, starboard, inside
1343			x.push(this.WL_RATIO_COEFF * ( -4 * boatLength) / 7); // Outrigger bow
1344			x.push(this.WL_RATIO_COEFF * ( -4 * boatLength) / 7); // Arm, front, starboard, outside
1345			x.push(this.WL_RATIO_COEFF * ( -0.6 * boatLength) / 7); // Arm, front, starboard, inside
1346			x.push(this.WL_RATIO_COEFF * ( -0.3 * boatLength) / 7);
1347
1348			// Length
1349			y.push((-4 * boatLength) / 7); // Nose
1350			// Starboard
1351			y.push((-3 * boatLength) / 7);
1352			y.push((-2 * boatLength) / 7);
1353			y.push((-1 * boatLength) / 7);
1354			y.push(( 0 * boatLength) / 7);
1355			y.push((-0.5 * boatLength) / 7);
1356			y.push(( 1.8 * boatLength) / 7);
1357			y.push(( 2.5 * boatLength) / 7);
1358			y.push(( 3 * boatLength) / 7);
1359			y.push(( 2.8 * boatLength) / 7);
1360			// Port
1361			y.push(( 2.8 * boatLength) / 7);
1362			y.push(( 3 * boatLength) / 7);
1363			y.push(( 2.5 * boatLength) / 7);
1364			y.push(( 1.8 * boatLength) / 7);
1365			y.push((-0.5 * boatLength) / 7);
1366			y.push(( 0 * boatLength) / 7);
1367			y.push((-1 * boatLength) / 7);
1368			y.push((-2 * boatLength) / 7);
1369			y.push((-3 * boatLength) / 7);
1370		}
1371		let xPoints = [];
1372		let yPoints = [];
1373
1374		// Rotation matrix:
1375		// | cos(alpha)  -sin(alpha) |
1376		// | sin(alpha)   cos(alpha) |
1377		// The center happens to be the middle of the boat.
1378
1379		let center = this.getCanvasCenter();
1380		let ptX = center.x;
1381		let ptY = center.y;
1382
1383		for (let i=0; i<x.length; i++) { // Rotation
1384			let dx = x[i] * Math.cos(Math.toRadians(trueHeading)) + (y[i] * (-Math.sin(Math.toRadians(trueHeading))));
1385			let dy = x[i] * Math.sin(Math.toRadians(trueHeading)) + (y[i] *   Math.cos(Math.toRadians(trueHeading)));
1386			xPoints.push(Math.round(ptX + dx));
1387			yPoints.push(Math.round(ptY + dy));
1388		}
1389		context.fillStyle = this.boatOverviewColorConfig.boatFillColor;
1390		context.beginPath();
1391		context.moveTo(xPoints[0], yPoints[0]);
1392		for (let i=1; i<xPoints.length; i++) {
1393			context.lineTo(xPoints[i], yPoints[i]);
1394		}
1395		context.closePath();
1396		context.fill();
1397		context.strokeStyle = this.boatOverviewColorConfig.boatOutlineColor;
1398		context.lineWidth = 2;
1399		context.stroke();
1400	}
1401
1402	drawBoatOverview() {
1403
1404		let currentStyle = this.className;
1405		if (this._previousClassName !== currentStyle || true) {
1406			// Reload
1407			//	console.log("Reloading CSS");
1408			try {
1409				this.boatOverviewColorConfig = this.getColorConfig(currentStyle);
1410			} catch (err) {
1411				// Absorb?
1412				console.log(err);
1413			}
1414			this._previousClassName = currentStyle;
1415		}
1416
1417		let context = this.canvas.getContext('2d');
1418
1419		if (this._width === 0 || this._height === 0) { // Not visible
1420			return;
1421		}
1422		// Set the canvas size from its container.
1423		this.canvas.width = this._width;
1424		this.canvas.height = this._height;
1425
1426		// Background
1427		let grd = context.createLinearGradient(0, 5, 0, this._height);
1428		grd.addColorStop(0, this.boatOverviewColorConfig.displayBackgroundGradient.from); // 0  Beginning
1429		grd.addColorStop(1, this.boatOverviewColorConfig.displayBackgroundGradient.to); // 1  End
1430		context.fillStyle = grd;
1431		context.fillRect(0, 0, this._width, this._height);
1432
1433		// The actual Graph:
1434		let maxSpeed = 5;
1435		if (this._withGPS) {
1436			maxSpeed = Math.max(maxSpeed, this._sog);
1437		}
1438		maxSpeed = Math.max(maxSpeed, this._bsp);
1439		if (this._withGPS && this._withWind && this._withTrueWind) {
1440			maxSpeed = Math.max(maxSpeed, this._tws);
1441		}
1442		if (this._withWind) {
1443			maxSpeed = Math.max(maxSpeed, this._aws);
1444		}
1445		this.speedScale = 5 * (Math.ceil(maxSpeed / 5));
1446
1447		let cWidth  = this._width;
1448		let cHeight = this._height;
1449
1450		// Circles
1451		let center = this.getCanvasCenter();
1452		let x = center.x;
1453		let y = center.y;
1454
1455		context.strokeStyle = this.boatOverviewColorConfig.gridColor;
1456		for (let circ=1; circ<=this.speedScale; circ++) {
1457			let radius = this._zoom * Math.round(circ * ((Math.min(cHeight, cWidth) / 2) / this.speedScale));
1458			context.beginPath();
1459			if (circ % 5 === 0) {
1460				context.lineWidth = 3;
1461			} else {
1462				context.lineWidth = 1;
1463			}
1464			context.arc(x, y, radius, 0, 2 * Math.PI);
1465			context.closePath();
1466			context.stroke();
1467		}
1468
1469		this.drawBoat(context, this._hdg);
1470		if (this._withWind && this._withTrueWind) {
1471			if (this._withGPS) {
1472				this.drawTrueWind(context);
1473				this.drawVW(context); // Speed Wind (Velocity)
1474			}
1475		}
1476		if (this._withWind) {
1477			this.drawAppWind(context);
1478		}
1479		this.drawBSP(context);
1480		if (this._withW) {
1481			this.drawNorths(context);
1482		}
1483		this.drawCMG(context);
1484		if (this._withGPS) {
1485			this.drawSOG(context);
1486		}
1487		if (this._withCurrent && this._withGPS) {
1488			this.drawCurrent(context);
1489		}
1490		if (this._withVMG && this._withGPS) {
1491			this.drawVMG(context);
1492		}
1493		// Display values
1494		// See http://www.w3schools.com/tags/ref_entities.asp, &deg; = &#176;
1495		context.fillStyle = this.boatOverviewColorConfig.nmeaDataDisplayColor;
1496		context.font="bold 16px Courier New";
1497		let txtY = 20;
1498		let space = 18;
1499		let col1 = 10, col2 = 90;
1500		context.fillText("BSP", col1, txtY);
1501		context.fillText(this._bsp + " kts", col2, txtY);
1502		txtY += space;
1503		context.fillText("HDG", col1, txtY);
1504		context.fillText(this._hdg.toFixed(0) + "° True", col2, txtY);
1505		if (this._withWind) {
1506			txtY += space;
1507			context.fillText("AWS", col1, txtY);
1508			context.fillText(this._aws + " kts", col2, txtY);
1509			txtY += space;
1510			context.fillText("AWA", col1, txtY);
1511			context.fillText(this._awa + "°", col2, txtY);
1512		}
1513
1514		if (this._withGPS) {
1515			context.fillStyle = this.boatOverviewColorConfig.gpsWsArrowColor;
1516			txtY += space;
1517			context.fillText("COG", col1, txtY);
1518			context.fillText(this._cog.toFixed(0) + "°", col2, txtY);
1519			txtY += space;
1520			context.fillText("SOG", col1, txtY);
1521			context.fillText(this._sog.toFixed(2) + " kts", col2, txtY);
1522		}
1523
1524		context.fillStyle = this.boatOverviewColorConfig.calculatedDataDisplayColor;
1525		if (this._withWind && this._withTrueWind && this._withGPS) {
1526			txtY += space;
1527			context.fillText("TWS", col1, txtY);
1528			context.fillText(this._tws.toFixed(2) + " kts", col2, txtY);
1529			txtY += space;
1530			context.fillText("TWA", col1, txtY);
1531			context.fillText(this._twa + "°", col2, txtY);
1532			txtY += space;
1533			context.fillText("TWD", col1, txtY);
1534			context.fillText(this._twd + "°", col2, txtY);
1535		}
1536		if (this._withCurrent && this._withGPS) {
1537			txtY += space;
1538			context.fillText("CDR", col1, txtY);
1539			context.fillText(this._cdr.toFixed(0) + "°", col2, txtY);
1540			txtY += space;
1541			context.fillText("CSP", col1, txtY);
1542			context.fillText(this._csp.toFixed(2) + " kts", col2, txtY);
1543		}
1544		txtY += space;
1545		context.fillText("leeway", col1, txtY);
1546		context.fillText(this._lwy.toFixed(2) + "°", col2, txtY);
1547		txtY += space;
1548		context.fillText("CMG", col1, txtY);
1549		context.fillText(this._cmg.toFixed(0) + "°", col2, txtY);
1550
1551		if (this._withVMG && this._withGPS) {
1552			let mess = ", ";
1553			if (this._vmgOnWind) {
1554				mess += "on wind";
1555			} else {
1556				mess += ("on WP [" + this._wpName + "]");
1557			}
1558			context.fillStyle = this.boatOverviewColorConfig.vmgDataDisplayColor;
1559			txtY += space;
1560			context.fillText("VMG", col1, txtY);
1561			context.fillText(this._vmg.toFixed(2) + " kts" + mess, col2, txtY);
1562		}
1563
1564		if (this._withW) {
1565			let hdm = this._hdg - this._Decl;
1566			let hdc = hdm - this._dev;
1567			while (hdm < 0) {
1568				hdm += 360;
1569			}
1570			while (hdc < 0) {
1571				hdc += 360;
1572			}
1573
1574			context.fillStyle = this.boatOverviewColorConfig.dDWDataDisplayColor;
1575			txtY += space;
1576			context.fillText("D", col1, txtY);
1577			context.fillText(this._Decl.toFixed(1) + "°", col2, txtY);
1578			txtY += space;
1579			context.fillText("d", col1, txtY);
1580			context.fillText(this._dev.toFixed(1) + "°", col2, txtY);
1581			txtY += space;
1582			context.fillText("W", col1, txtY);
1583			context.fillText((this._Decl + this._dev).toFixed(1) + "°", col2, txtY);
1584			txtY += space;
1585			context.fillText("HDM", col1, txtY);
1586			context.fillText(hdm.toFixed(1) + "°", col2, txtY);
1587			txtY += space;
1588			context.fillText("HDC", col1, txtY);
1589			context.fillText(hdc.toFixed(1) + "°", col2, txtY);
1590		}
1591
1592	}
1593}
1594
1595class Point {
1596	constructor(x, y) {
1597		this.x = x;
1598		this.y = y;
1599	}
1600}
1601
1602
1603class Line {
1604
1605// Line with arrow head
1606	constructor(x1, y1, x2, y2) {
1607		this.x1 = x1;
1608		this.y1 = y1;
1609		this.x2 = x2;
1610		this.y2 = y2;
1611
1612		this.HEAD_LENGTH = 20;
1613		this.HEAD_WIDTH  =  6;
1614	}
1615
1616
1617	static rotate(p, angle) {
1618		return new Point(Math.round((p.x * Math.cos(Math.toRadians(angle))) + (p.y * Math.sin(Math.toRadians(angle)))),
1619				Math.round((p.x * -Math.sin(Math.toRadians(angle))) + (p.y * Math.cos(Math.toRadians(angle)))));
1620	}
1621
1622	drawWithArrowhead(ctx) {
1623		this.drawWithArrowheads(ctx, false);
1624	}
1625
1626	drawWithArrowheads(ctx, both) {
1627		if (both === undefined) {
1628			both = true;
1629		}
1630		// draw the line
1631		ctx.beginPath();
1632		ctx.moveTo(this.x1, this.y1);
1633		ctx.lineTo(this.x2, this.y2);
1634		ctx.stroke();
1635
1636		if (both) {
1637			// draw the starting arrowhead
1638			let startRadians = Math.atan((this.y2 - this.y1) / (this.x2 - this.x1));
1639			startRadians += ((this.x2 > this.x1) ? -90 : 90) * Math.PI / 180;
1640			this.drawArrowhead(ctx, this.x1, this.y1, startRadians);
1641		}
1642		// draw the ending arrowhead
1643		let endRadians = Math.atan((this.y2 - this.y1) / (this.x2 - this.x1));
1644		endRadians += ((this.x2 > this.x1) ? 90 : -90) * Math.PI / 180;
1645		this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
1646	}
1647
1648	drawWithAnemoArrowheads(ctx) {
1649		// draw the line
1650		ctx.beginPath();
1651		ctx.moveTo(this.x1, this.y1);
1652		ctx.lineTo(this.x2, this.y2);
1653		ctx.stroke();
1654
1655		let endRadians = Math.atan((this.y2 - this.y1) / (this.x2 - this.x1));
1656		endRadians += ((this.x2 > this.x1) ? 90 : -90) * Math.PI / 180;
1657		this.drawArrowhead(ctx, this.x2 - (this.x2 - this.x1) / 2, this.y2 - (this.y2 - this.y1) / 2, endRadians);
1658	}
1659
1660	drawHollowArrow(ctx) {
1661		let headLength = 30;
1662		let arrowWidth = 10;
1663		let headWidth = 20;
1664
1665		let dir = Utilities.getDir((this.x1 - this.x2), (this.y2 - this.y1));
1666		let len = Math.sqrt(((this.x1 - this.x2) * (this.x1 - this.x2)) + ((this.y2 - this.y1) * (this.y2 - this.y1)));
1667
1668		let one, two, three, four, five, six, seven, eight;
1669		one = new Point(0, 0);
1670		two = new Point(-arrowWidth / 2, 0);
1671		three = new Point(-arrowWidth / 2, -(Math.round(len - headLength)));
1672		four = new Point(-headWidth / 2, -(Math.round(len - headLength)));
1673		five = new Point(0, -Math.round(len)); // to
1674		six = new Point(headWidth / 2, -(Math.round(len - headLength)));
1675		seven = new Point(arrowWidth / 2, -(Math.round(len - headLength)));
1676		eight = new Point(arrowWidth / 2, 0);
1677		one = Line.rotate(one, -dir);
1678		two = Line.rotate(two, -dir);
1679		three = Line.rotate(three, -dir);
1680		four = Line.rotate(four, -dir);
1681		five = Line.rotate(five, -dir);
1682		six = Line.rotate(six, -dir);
1683		seven = Line.rotate(seven, -dir);
1684		eight = Line.rotate(eight, -dir);
1685
1686		let x = [];
1687		let y = [];
1688
1689		x.push(this.x1 + one.x);
1690		x.push(this.x1 + two.x);
1691		x.push(this.x1 + three.x);
1692		x.push(this.x1 + four.x);
1693		x.push(this.x1 + five.x);
1694		x.push(this.x1 + six.x);
1695		x.push(this.x1 + seven.x);
1696		x.push(this.x1 + eight.x);
1697
1698		y.push(this.y1 + one.y);
1699		y.push(this.y1 + two.y);
1700		y.push(this.y1 + three.y);
1701		y.push(this.y1 + four.y);
1702		y.push(this.y1 + five.y);
1703		y.push(this.y1 + six.y);
1704		y.push(this.y1 + seven.y);
1705		y.push(this.y1 + eight.y);
1706
1707		ctx.beginPath();
1708		ctx.moveTo(x[0], y[0]);
1709		for (let i = 1; i < x.length; i++) {
1710			ctx.lineTo(x[i], y[i]);
1711		}
1712		ctx.closePath();
1713		ctx.stroke();
1714	}
1715
1716	drawArrowhead(ctx, x, y, radians) {
1717		ctx.save();
1718		ctx.beginPath();
1719		ctx.translate(x, y);
1720		ctx.rotate(radians);
1721		ctx.moveTo(0, 0);
1722		ctx.lineTo(this.HEAD_WIDTH, this.HEAD_LENGTH);
1723		ctx.lineTo(-this.HEAD_WIDTH, this.HEAD_LENGTH);
1724		ctx.closePath();
1725		ctx.restore();
1726		ctx.fill();
1727	}
1728}
1729
1730// Associate the tag and the class
1731window.customElements.define(BOAT_OVERVIEW_TAG_NAME, BoatOverview);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.