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.