PageSourceSearch

https://olivierld.github.io/webcomponents/widgets/satelliteplotter/SatellitePlotter.js

js olivierld.github.io collected 2026-10-03 09:43:10 UTC 11,084 bytes, 380 lines download raw bytes

1const spVerbose = false;
2const SAT_PLOT_TAG_NAME = 'satellite-plotter';
3
4/*
5* See custom properties in CSS.
6* =============================
7* @see https://developer.mozilla.org/en-US/docs/Web/CSS/
8* Relies on those elements:
9*
10.xxxxxxxx {
11	--border-color: rgba(0, 0, 0, 0);
12	--with-gradient: true;
13	--display-background-gradient-from: LightGrey;
14	--display-background-gradient-to: white;
15	--with-display-shadow: true;
16	--shadow-color: rgba(0, 0, 0, 0.75);
17	--line-color: 'grey';
18	--font: Arial;
19}
20*/
21
22/**
23 * Recurse from the top down, on styleSheets and cssRules
24 *
25 * document.styleSheets[0].cssRules[2].selectorText returns ".analogdisplay"
26 * document.styleSheets[0].cssRules[2].cssText returns ".analogdisplay { --hand-color: red;  --face-color: white; }"
27 * document.styleSheets[0].cssRules[2].style.cssText returns "--hand-color: red; --face-color: white;"
28 *
29 * spine-case to camelCase
30 */
31const defaultSatPlotColorConfig = {
32	borderColor: 'black',
33	withGradient: true,
34	displayBackgroundGradient: {
35		from: 'DarkGrey',
36		to: 'black'
37	},
38	withDisplayShadow: true,
39	shadowColor: 'black',
40	lineColor: 'DarkGrey',
41	font: 'Arial'
42};
43
44import * as Utilities from "../utilities/Utilities.js";
45
46/* global HTMLElement */
47class SatellitePlotter extends HTMLElement {
48
49	static get observedAttributes() {
50		return [
51			"width",            // Integer. Canvas width
52			"height",           // Integer. Canvas height
53			"with-border",      // Boolean
54			"sat-in-view"       // Stringified JSON
55		];
56	}
57
58	constructor() {
59		super();
60		this._shadowRoot = this.attachShadow({mode: 'open'}); // 'open' means it is accessible from external JavaScript.
61		// create and append a <canvas>
62		this.canvas = document.createElement("canvas");
63		let fallbackElemt = document.createElement("h1");
64		let content = document.createTextNode("This is an AnalogDisplay, on an HTML5 canvas");
65		fallbackElemt.appendChild(content);
66		this.canvas.appendChild(fallbackElemt);
67		this.shadowRoot.appendChild(this.canvas);
68
69		// Default values
70		this._width            = 150;
71		this._height           = 150;
72		this._with_border      = true;
73
74		this._satellites = {};
75
76		this._previousClassName = "";
77		this.satPlotColorConfig = defaultSatPlotColorConfig; // Init
78
79		if (spVerbose) {
80			console.log("Data in Constructor:", this._value);
81		}
82	}
83
84	// Called whenever the custom element is inserted into the DOM.
85	connectedCallback() {
86		if (spVerbose) {
87			console.log("connectedCallback invoked");
88		}
89	}
90
91	// Called whenever the custom element is removed from the DOM.
92	disconnectedCallback() {
93		if (spVerbose) {
94			console.log("disconnectedCallback invoked");
95		}
96	}
97
98	// Called whenever an attribute is added, removed or updated.
99	// Only attributes listed in the observedAttributes property are affected.
100	attributeChangedCallback(attrName, oldVal, newVal) {
101		if (spVerbose) {
102			console.log("attributeChangedCallback invoked on " + attrName + " from " + oldVal + " to " + newVal);
103		}
104		switch (attrName) {
105			case "width":
106				this._width = parseInt(newVal);
107				break;
108			case "height":
109				this._height = parseInt(newVal);
110				break;
111			case "with-border":
112				this._with_border = ("true" === newVal);
113				break;
114			case "sat-in-view":
115				this._satellites = JSON.parse(newVal);
116				break;
117			default:
118				break;
119		}
120		this.repaint();
121	}
122
123	// Called whenever the custom element has been moved into a new document.
124	adoptedCallback() {
125		if (spVerbose) {
126			console.log("adoptedCallback invoked");
127		}
128	}
129
130	set width(val) {
131		this.setAttribute("width", val);
132	}
133	set height(val) {
134		this.setAttribute("height", val);
135	}
136	set withBorder(val) {
137		this.setAttribute("with-border", val);
138	}
139	set satInView(val) {
140		this.setAttribute("sat-in-view", val);
141	}
142	set shadowRoot(val) {
143		this._shadowRoot = val;
144	}
145
146	get width() {
147		return this._width;
148	}
149	get height() {
150		return this._height;
151	}
152	get withBorder() {
153		return this._with_border;
154	}
155	get satInView() {
156		return this._satellites;
157	}
158	get shadowRoot() {
159		return this._shadowRoot;
160	}
161
162	// Component methods
163	repaint() {
164		this.drawDisplay();
165	}
166
167	setSatellites(sats) {
168		this._satellites = sats;
169	}
170
171	getColorConfig(classNames) {
172		let colorConfig = defaultSatPlotColorConfig;
173		let classes = classNames.split(" ");
174		for (let cls=0; cls<classes.length; cls++) {
175			let className = classes[cls];
176			for (let s=0; s<document.styleSheets.length; s++) {
177				//		console.log("Walking though ", document.styleSheets[s]);
178				try {
179					for (let r = 0; document.styleSheets[s].cssRules !== null && r < document.styleSheets[s].cssRules.length; r++) {
180						let selector = document.styleSheets[s].cssRules[r].selectorText;
181						//			console.log(">>> ", selector);
182						if (selector !== undefined && (selector === '.' + className || (selector.indexOf('.' + className) > -1 && selector.indexOf(SAT_PLOT_TAG_NAME) > -1))) { // Cases like "tag-name .className"
183							//				console.log("  >>> Found it! [%s]", selector);
184							let cssText = document.styleSheets[s].cssRules[r].style.cssText;
185							let cssTextElems = cssText.split(";");
186							cssTextElems.forEach((elem) => {
187								if (elem.trim().length > 0) {
188									let keyValPair = elem.split(":");
189									let key = keyValPair[0].trim();
190									let value = keyValPair[1].trim();
191									switch (key) {
192										case '--border-color':
193											colorConfig.borderColor = value;
194											break;
195										case '--with-gradient':
196											colorConfig.withGradient = (value === 'true');
197											break;
198										case '--display-background-gradient-from':
199											colorConfig.displayBackgroundGradient.from = value;
200											break;
201										case '--display-background-gradient-to':
202											colorConfig.displayBackgroundGradient.to = value;
203											break;
204										case '--with-display-shadow':
205											colorConfig.withDisplayShadow = (value === 'true');
206											break;
207										case '--shadow-color':
208											colorConfig.shadowColor = value;
209											break;
210										case '--line-color':
211											colorConfig.lineColor = value;
212											break;
213										case '--font':
214											colorConfig.font = value;
215											break;
216										default:
217											break;
218									}
219								}
220							});
221						}
222					}
223				} catch (err) {
224					// Absorb
225				}
226			}
227		}
228		return colorConfig;
229	}
230
231	drawDisplay() {
232
233		let currentStyle = this.className;
234		if (this._previousClassName !== currentStyle || true) {
235			// Reload
236			//	console.log("Reloading CSS");
237			try {
238				this.satPlotColorConfig = this.getColorConfig(currentStyle);
239			} catch (err) {
240				// Absorb?
241				console.log(err);
242			}
243			this._previousClassName = currentStyle;
244		}
245
246		let borderColor = this.satPlotColorConfig.borderColor;
247
248		let context = this.canvas.getContext('2d');
249		context.fillStyle = 'transparent';
250		context.fillRect(0, 0, this._width, this._height);
251
252		let radius = (Math.min(this._width, this._height) / 2) * 0.90;
253
254		// Set the canvas size from its container.
255		this.canvas.width = this._width;
256		this.canvas.height = this._height;
257
258		let center = {
259			x: this.canvas.width / 2,
260			y: (this.canvas.height / 2)
261		};
262
263		context.beginPath();
264
265		if (this.withBorder === true) {
266			context.strokeStyle = borderColor;
267			// context.arc(this.canvas.width / 2, radius + 10, radius, 0, 2 * Math.PI, false);
268			context.arc(center.x, center.y, radius, 0, 2 * Math.PI, false);
269			context.lineWidth = 15;
270			context.stroke();
271		}
272
273		if (this.satPlotColorConfig.withGradient) {
274			let grd = context.createLinearGradient(0, 5, 0, radius);
275			grd.addColorStop(0, this.satPlotColorConfig.displayBackgroundGradient.from);// 0  Beginning
276			grd.addColorStop(1, this.satPlotColorConfig.displayBackgroundGradient.to);  // 1  End
277			context.fillStyle = grd;
278		} else {
279			context.fillStyle = this.satPlotColorConfig.displayBackgroundGradient.to;
280		}
281		if (this.satPlotColorConfig.withDisplayShadow) {
282			context.shadowOffsetX = 3;
283			context.shadowOffsetY = 3;
284			context.shadowBlur = 3;
285			context.shadowColor = this.satPlotColorConfig.shadowColor;
286		} else {
287			context.shadowOffsetX = 0;
288			context.shadowOffsetY = 0;
289			context.shadowBlur = 0;
290			context.shadowColor = undefined;
291		}
292		context.lineJoin = "round";
293		context.fill();
294
295		context.closePath();
296
297		// Axis: N-S, E-W, NE-SW, NW-SE
298		context.strokeStyle = this.satPlotColorConfig.lineColor;
299		context.lineWidth = 1;
300		context.setLineDash([3, 3]); // 3px dash, 3px space
301		context.beginPath();
302		// N-S
303		context.moveTo(center.x, center.y - radius); // N
304		context.lineTo(center.x, center.y + radius); // S
305		// E-W
306		context.moveTo(center.x - radius, center.y); // W
307		context.lineTo(center.x + radius, center.y); // E
308		// NW-SE
309		context.moveTo(center.x - (radius * Math.sin(Math.PI / 4)), center.y - (radius * Math.sin(Math.PI / 4))); // NW
310		context.lineTo(center.x + (radius * Math.sin(Math.PI / 4)), center.y + (radius * Math.sin(Math.PI / 4))); // SE
311		// NE-SW
312		context.moveTo(center.x - (radius * Math.sin(Math.PI / 4)), center.y + (radius * Math.sin(Math.PI / 4))); // NE
313		context.lineTo(center.x + (radius * Math.sin(Math.PI / 4)), center.y - (radius * Math.sin(Math.PI / 4))); // SW
314
315		// Altitude circles 30, 60.
316		context.moveTo(center.x + (radius / 3), center.y); // 0 degrees is actually E
317		context.arc(center.x, center.y, radius / 3, 0, 2 * Math.PI, false); // 60 degrees
318		context.arc(center.x, center.y, 2 * radius / 3, 0, 2 * Math.PI, false); // 30 degrees
319
320		context.stroke();
321		context.closePath();
322
323		context.setLineDash([0]); // 3px dash, 3px space
324
325		// Plot satellites.
326		const SAT_RADIUS = 6;
327		if (this._satellites !== undefined && this._satellites !== {}) {
328			for (let satNum in this._satellites) {
329				context.beginPath();
330
331				context.fillStyle = getSNRColor(this._satellites[satNum].snr);
332//              let satCircleRadius = radius * (Math.cos(toRadians(demoSat[i].el)));
333				let satCircleRadius = radius * ((90 - this._satellites[satNum].elevation) / 90);
334				let centerSat = {
335					x: center.x + (satCircleRadius * Math.sin(Math.toRadians(this._satellites[satNum].azimuth))),
336					y: center.y - (satCircleRadius * Math.cos(Math.toRadians(this._satellites[satNum].azimuth)))
337				};
338				context.arc(centerSat.x, centerSat.y, SAT_RADIUS, 0, 2 * Math.PI, false);
339
340				let text = this._satellites[satNum].svID;
341				let scale = 1; // TODO Fix this
342				context.font = "bold " + Math.round(scale * 12) + "px " + this.satPlotColorConfig.font; // "bold 40px Arial"
343				let metrics = context.measureText(text);
344				let len = metrics.width;
345
346				context.fill();
347				context.stroke();
348				context.fillText(text, centerSat.x - (len / 2), centerSat.y - SAT_RADIUS - 2);
349
350				context.closePath();
351			}
352		}
353	}
354
355	getSNRColor(snr) {
356		let c = 'lightGray';
357		if (snr !== undefined && snr !== null) {
358			if (snr > 0) {
359				c = 'red';
360			}
361			if (snr > 10) {
362				c = 'orange';
363			}
364			if (snr > 20) {
365				c = 'yellow';
366			}
367			if (snr > 30) {
368				c = 'lightGreen';
369			}
370			if (snr > 40) {
371				c = 'green';
372			}
373		}
374		return c;
375	}
376
377}
378
379// Associate the tag and the class
380window.customElements.define(SAT_PLOT_TAG_NAME, SatellitePlotter);

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.