1/** 2 * Definition import and declaration to support IntelliSense completion for development 3 * @typedef {import('/static/notifications/js/notifications.mjs').Notifications} Notifications 4 * @type Notifications 5 */ 6var notifications 7 8const bbcov_layer_structure = { 9 10 'root':'bbcov', 11 'source': { 12 'va': 'vacoll', 13 'fcc': 'fccbdc', 14 }, 15 'coverage': { 16 '0': '0p', 17 '80': '80p', 18 '85': '85p', 19 '95': '95p', 20 }, 21 'speed': { 22 '25_3': '25d3u', 23 '100_20': '100d20u', 24 '100': '100d', 25 '250': '250d', 26 '1000': '1000d', 27 }, 28 'percent': { 29 '0': '0p', 30 '80': '80p', 31 '85': '85p', 32 '95': '95p', 33 }, 34 'tech': { 35 'any': 'any', 36 'cable': 'cable', 37 'dsl': 'dsl', 38 'fiber': 'fiber', 39 'fixed': 'fixedwireless', 40 }, 41}; 42 43const bbcov_layer_structure_cql = { 44 45 'speed': { 46 // '25d3u': '25_3', 47 '100d20u': '100_20', 48 '100d': '100', 49 '250d': '250', 50 '1000d': '1000', 51 }, 52}; 53 54var bbcov_layer_options = { 55 'speed': bbcov_layer_structure['speed']['100_20'], 56 'tech': bbcov_layer_structure['tech']['any'], 57 'percent': bbcov_layer_structure['percent']['80'], 58} 59 60var layer_template_bbcov = { 61 "displayName": "", 62 "legendName": "", 63 "date": "", 64 "url": "https://cgit06.cc.vt.edu/geoserver/broadband/wms", 65 "opacity": 0.50, 66 "options": { 67 "service": "WMS", 68 "version": "1.1.1", 69 "request": "GetMap", 70 "srs": "EPSG:3857", 71 "layers": "", 72 "transparent": true, 73 "tiled": true, 74 "format": "image/png", 75 "styles": "", 76 "width": "256", 77 "height": "256", 78 }, 79 "color":"", 80 "zIndex": 2 81}; 82 83var useCQL = false; 84 85var bbcov_map_layers = {}; 86 87// Multi-Layer System Variables 88var activeCoverageLayers = []; 89const availableColors = new Set(['ff2626', '3232fe', '00ff00']); // old ones: ['fb6c05', 'ec4ffd', '8105fb'] 90var maxLayers = 3; 91 92// Multi-Layer Helper Functions 93/** 94 * Return the next available color from the set of available colors 95 * @returns {String|undefined} Either a color string or undefined if none is available 96 */ 97function getNextAvailableColor() { 98 // Build a set from the colors used in the active coverage layers, diff the color list against it, and return the first unused color 99 return [...availableColors.difference(new Set(activeCoverageLayers.map(l => l.color)))][0] 100} 101 102function addTestLayer(speed, tech, coverage, version, color, layerId) { 103 if (activeCoverageLayers.length >= maxLayers) { 104 notifications.warn('Maximum 3 layers allowed'); 105 return false; 106 } 107 108 activeCoverageLayers.push({ 109 id: layerId, speed, tech, coverage, version, color, 110 displayName: buildLayerDisplayName(speed, tech, coverage, version) 111 }); 112 return true; 113} 114 115function removeTestLayer(layerId) { 116 activeCoverageLayers = activeCoverageLayers.filter(layer => layer.id !== layerId); 117 removeCustomLayerFromLegend(layerId); 118 updateActiveLayersDisplay(); 119} 120 121$( document ).ready(function() { 122 123 bbcovLayerRefresh(); 124}); 125 126function bbcovLayerRefresh(){ 127 128 bbcov_map_layers = { 129 'va_collected': { 130 'id': `${bbcov_layer_structure['root']}_${bbcov_layer_structure['source']['va']}_${bbcov_layer_options['speed']}_${bbcov_layer_options['percent']}_${bbcov_layer_options['tech']}`, 131 "displayName": "VA Collected", 132 "legendName": "VA Collected", 133 "date": "20240308", 134 "opacity": 0.5, 135 "options": { 136 "layers": "broadband:bbcoverage_vacolleced", 137 "styles": `${bbcov_layer_structure['root']}_${bbcov_layer_structure['source']['va']}_${bbcov_layer_options['speed']}_${bbcov_layer_options['percent']}_${bbcov_layer_options['tech']}`, 138 }, 139 "color": "#7cb5db", 140 "zIndex": 4 141 }, 142 'fcc_any': { 143 'id': `${bbcov_layer_structure['root']}_${bbcov_layer_structure['source']['fcc']}_${bbcov_layer_options['speed']}_${bbcov_layer_options['percent']}_${bbcov_layer_options['tech']}`, 144 "displayName": "FCC BDC", 145 "legendName": "FCC BDC", 146 "date": "20241231", 147 "opacity": 0.65, 148 "options": { 149 "layers": "broadband:bbcoverage_fccbdc", 150 "styles": `${bbcov_layer_structure['root']}_${bbcov_layer_structure['source']['fcc']}_${bbcov_layer_options['speed']}_${bbcov_layer_options['percent']}_${bbcov_layer_options['tech']}`, 151 }, 152 "color": "#ec4ffd", 153 "zIndex": 2 154 } 155 } 156} 157 158function getCQLfilter(layerKey){ 159 160 var returnDict = { 161 "styles": '', 162 "CQL_FILTER": "", 163 } 164 165 var coverage = 0; 166 var speed = bbcov_layer_structure_cql['speed'][bbcov_layer_options['speed']]; 167 var operand = '>'; 168 169 switch(layerKey){ 170 case 'va_collected': 171 returnDict["styles"] = "bbcov_vacollected_80p"; 172 coverage = 80; 173 operand = '>='; 174 break; 175 case 'va_any': 176 returnDict["styles"] = "bbcov_vacollected_0p"; 177 coverage = 0; 178 operand = '>'; 179 break; 180 case 'va_80': 181 returnDict["styles"] = "bbcov_vacollected_80p"; 182 coverage = 80; 183 operand = '>='; 184 break; 185 case 'va_85': 186 returnDict["styles"] = "bbcov_vacollected_85p"; 187 coverage = 85; 188 operand = '>='; 189 break; 190 case 'va_95': 191 returnDict["styles"] = "bbcov_vacollected_95p"; 192 coverage = 95; 193 operand = '>='; 194 break; 195 case 'fcc_any': 196 returnDict["styles"] = "bbcov_fcc_0p"; 197 coverage = 0; 198 operand = '>'; 199 break; 200 } 201 202 switch(bbcov_layer_options['tech']){ 203 case 'any': 204 returnDict["CQL_FILTER"] = `cable_${speed}${operand}${coverage} OR dsl_${speed}${operand}${coverage} OR fiber_${speed}${operand}${coverage} OR fixedwireless_${speed}${operand}${coverage}`; 205 break; 206 case 'cable': 207 returnDict["CQL_FILTER"] = `cable_${speed}${operand}${coverage}`; 208 break; 209 case 'dsl': 210 returnDict["CQL_FILTER"] = `dsl_${speed}${operand}${coverage}`; 211 break; 212 case 'fiber': 213 returnDict["CQL_FILTER"] = `fiber_${speed}${operand}${coverage}`; 214 break; 215 case 'fixedwireless': 216 returnDict["CQL_FILTER"] = `fixedwireless_${speed}${operand}${coverage}`; 217 break; 218 } 219 220 return returnDict; 221} 222 223function bbcovSetLayerOption(option,value){ 224 225 var layer_name; 226 227 bbcov_layer_options[option] = bbcov_layer_structure[option][value]; 228 229 if(option == 'speed'){ 230 // Updating values in legend 231 let upDown = value.split("_"); 232 $("#lg-bb-down").html(upDown[0] + " mbps"); 233 234 if(upDown.length == 1) { 235 $("#lg-bb-up").html("Any"); 236 } else { 237 $("#lg-bb-up").html(upDown[1] + " mbps"); 238 } 239 } else if ( option == 'tech') { 240 let mapTechToDisplayName = { 241 'any':'All Technologies', 242 'cable':'Cable', 243 'dsl':'DSL/Copper', 244 'fiber':'Fiber', 245 'fixedwireless':'Fixed Wireless' 246 } 247 $('#lg-bb-tech').html(mapTechToDisplayName[value]) 248 } else if (option=='percent'){ 249 let mapPercentToDisplayName = { 250 '0':'At least One Address', 251 '80':'80%', 252 '85':'85%', 253 '95':'95%' 254 } 255 $('#lg-bb-percent').html(mapPercentToDisplayName[value]) 256 } 257 258 $('.bbcov-pick-layer').each(function () { 259 260 if ($(this).is(':checked')){ 261 layer_name = bbcov_map_layers[$(this).val()]['id']; 262 bbMAP.removeLayer(layer_name); 263 } 264 }); 265 266 bbcovLayerRefresh(); 267 268 $('.bbcov-pick-layer').each(function () { 269 270 if ($(this).is(':checked')){ 271 layer_name = bbcov_map_layers[$(this).val()]['id']; 272 bbcovAddLayer($(this).val(),layer_name); 273 } 274 }); 275 276 fabricLayerRefresh(); 277} 278 279$('.bbcov-pick-layer').change(function(event) { 280 281 var layer_name = bbcov_map_layers[$(this).val()]['id']; 282 if ($(this).is(':checked')){ 283 bbcovAddLayer($(this).val(),layer_name); 284 } 285 else { 286 bbMAP.removeLayer(layer_name); 287 } 288}); 289 290function bbcovAddLayer(layerKey,LayerName){ 291 292 var layerDict = {}; 293 var layerDictOptions = {}; 294 295 Object.assign(layerDict, layer_template_bbcov);
296 Object.assign(layerDictOptions, layer_template_bbcov['options']); 297 298 for (const [key, value] of Object.entries(bbcov_map_layers[layerKey])) { 299 layerDict[key] = value; 300 } 301 302 for (const [key, value] of Object.entries(bbcov_map_layers[layerKey]['options'])) { 303 layerDictOptions[key] = value; 304 } 305 306 layerDict['options'] = layerDictOptions; 307 308 if (useCQL){ 309 var cqlDict = getCQLfilter(layerKey); 310 layerDict['options']['styles'] = cqlDict['styles']; 311 layerDict['options']['CQL_FILTER'] = cqlDict['CQL_FILTER']; 312 } 313 314 bbcovLayerRefresh(); 315 316 bbMAP.addLayer(LayerName,bbLAYERS.createLayer(LayerName,layerDict),layerDict['legendName']); 317} 318 319function loadMultiLayer(layerId) { 320 const layer = activeCoverageLayers.find(l => l.id === layerId); 321 if (!layer) return; 322 323 const layerIndex = activeCoverageLayers.indexOf(layer); 324 const layerDict = { 325 ...layer_template_bbcov, 326 displayName: layer.displayName, 327 legendName: layer.displayName, 328 color: layer.color, 329 opacity: 0.40 - (layerIndex * 0.05), 330 }; 331 layerDict.options = { 332 ...layerDict.options, 333 layers: 'broadband:bbcoverage_vacollected_geoview', 334 styles: layer.color, 335 viewparams: buildViewParams(layer), 336 } 337 338 bbMAP.addLayer(layerId, bbLAYERS.createLayer(layerId, layerDict), layer.displayName); 339 340 addCustomLayerToLegend(layerId); 341} 342 343function buildViewParams(layer) { 344 const speedParam = layer.speed; 345 const techParam = layer.tech; 346 const coverageParam = layer.coverage; 347 const coverageVersion = layer.version 348 return `tech_speed:${techParam}_${speedParam};percent:${coverageParam};version_id:${coverageVersion}`; 349} 350 351function addCustomLayerToLegend(layerId) { 352 const layer = activeCoverageLayers.find(l => l.id === layerId); 353 if (!layer) return; 354 355 document.getElementById('lg-bb-custom-section').style.display = '' 356 357 const layerIndex = activeCoverageLayers.indexOf(layer); 358 359 const legendHTML = ` 360 <div id="lg-custom-${layerId}" class="horizontal-flex" style="margin-bottom: 5px;"> 361 <div class="circle-legend legend-override-${layer.color}"> </div> 362 <div style="font-size: 0.9em; margin-left: 8px;"> 363 <div>
363${layer.displayName}</div> 364 </div> 365 <!--<div>layer Index: ${layerIndex}</div>--> 366 </div>`; 367 368 document.getElementById('lg-bb-custom-layers').insertAdjacentHTML('afterbegin', legendHTML); 369} 370 371function removeCustomLayerFromLegend(layerId) { 372 document.getElementById(`lg-custom-${layerId}`)?.remove(); 373 374 if (document.getElementById('lg-bb-custom-layers').children.length === 0) { 375 document.getElementById('lg-bb-custom-section').style.display = 'none' 376 } 377} 378 379function addCustomLayerFromModalSettings(speed, tech, coverage, version) { 380 // Check max layers 381 if (activeCoverageLayers.length >= maxLayers) { 382 notifications.warn(`Maximum ${maxLayers} custom layers allowed. Please remove a layer first.`); 383 return false; 384 } 385 // Check duplicate 386 if (layerAlreadyExists(speed, tech, coverage, version)) { 387 notifications.warn('A layer with these exact settings already exists.'); 388 return false; 389 } 390 // Check available color 391 const color = getNextAvailableColor(); 392 if (!color) { 393 notifications.warn('No colors available.'); 394 return false; 395 } 396 397 const layerId = `custom-layer-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`; 398 399 // Create the layer 400 const success = addTestLayer(speed, tech, coverage, version, color, layerId); 401 402 if (success) { 403 loadMultiLayer(layerId); 404 updateActiveLayersDisplay(); 405 return true; 406 } 407 408 return false; 409} 410 411function layerAlreadyExists(speed, tech, coverage, version) { 412 return activeCoverageLayers.some(layer => 413 layer.speed === speed && 414 layer.tech === tech && 415 layer.coverage === coverage && 416 layer.version === version 417 ); 418} 419 420const techCodeLabels = { 421 'any': 'Any Tech', 422 'cable': 'Cable', 423 'dsl': 'DSL', 424 'fiber': 'Fiber', 425 'fixedwireless': 'Fixed Wireless' 426} 427function resolveTech(tech){ 428 return techCodeLabels[tech] || tech 429} 430 431const coverageLabels = {'0': 'Any Coverage'} 432function resolveCoverageLabel(coverage){ 433 return coverageLabels[coverage] || coverage + '%' 434} 435 436function buildLayerDisplayName(speed, tech, coverage, version) { 437 const speedDisplay = speed.replace('_', '/'); 438 const techDisplay = resolveTech(tech); 439 440 const coverageDisplay = resolveCoverageLabel(coverage); 441 const coverageVersion = globalThis.coverageVersions.find(entry => entry.id === version)?.name // TODO: Change this to use data's coverageVersions() 442 443 return `${speedDisplay} Mbps ${techDisplay} ${coverageDisplay} - ${coverageVersion}`; 444} 445 446function updateActiveLayersDisplay() { 447 const container = document.getElementById('active-custom-layers'); 448 const addButton = document.getElementById('add-custom-layer-btn'); 449 450 container.replaceChildren(); 451 452 activeCoverageLayers.forEach((layer, index) => { 453 const layerNumber = index + 1; 454 455 // Parse speed values 456 const speedParts = layer.speed.split('_'); 457 const downloadSpeed = speedParts[0] || '100'; 458 // If no underscore, it's download-only, so upload is "Any" 459 const uploadSpeed = speedParts.length > 1 ? speedParts[1] : 'Any'; 460 461 // Format technology display 462 const techDisplay = resolveTech(layer.tech); 463 464 // Format coverage display 465 const coverageDisplay = resolveCoverageLabel(layer.coverage); 466 467 const coverageVersion = globalThis.coverageVersions.find(entry => entry.id === layer.version)?.name // TODO: Change this to use data's coverageVersions() 468 469 const layerHTML = ` 470 <div class="custom-layer-item layer-sidebar-override override-${layer.color}"> 471 <!-- Colored square box in top-left corner --> 472 <div class="custom-layer-colored-box" style="background-color: #${layer.color};"> 473 <!-- Checkbox with tick inside the colored box --> 474 <div class="custom-layer-checkbox" style="background-color: #${layer.color};"> 475 <i class="fa-solid fa-check"></i> 476 </div> 477 </div> 478 479 <!-- Header section with fixed positioning and grey background --> 480 <div class="custom-layer-header"> 481 <div class="toggle-custom-layer-btn toggle-on" 482 data-layer-id="${layer.id}" 483 title="Toggle visibility"> 484 <i class="fa-duotone fa-solid fa-toggle-large-on fa-rotate-180" data-icon-on></i> 485 <i class="fa-duotone fa-solid fa-toggle-large-off fa-rotate-180" data-icon-off></i> 486 </div> 487 <div class="custom-layer-title"> 488 Coverage Layer #${layerNumber} 489 </div> 490 <div class="remove-custom-layer-btn" 491 data-layer-id="${layer.id}" 492 title="Remove this layer"> 493 <i class="fa-solid fa-xmark"></i> 494 </div> 495 </div> 496 497 <div class="custom-layer-content"> 498 <!-- Speed --> 499 <div class="custom-layer-parameter"> 500 <strong class="custom-layer-parameter-label"> 501 <i class="fa-solid fa-gauge-high"></i> 502 Speed: 503 </strong>
504 <span class="custom-layer-parameter-value"> 505 <i class="fa-regular fa-down-long custom-layer-speed-icon"></i> 506 <span>${downloadSpeed} <span class="custom-layer-speed-value-small">mbps</span></span> 507 <span class="custom-layer-speed-value"> 508 <i class="fa-regular fa-up-long custom-layer-speed-icon"></i> 509 ${uploadSpeed === 'Any' 510 ? '<span>Any</span>' 511 : `<span>${uploadSpeed} <span class="custom-layer-speed-value-small">mbps</span></span>`} 512 </span> 513 </span> 514 </div> 515 516 <!-- Technology --> 517 <div class="custom-layer-parameter"> 518 <strong class="custom-layer-parameter-label"> 519 <i class="fa-solid fa-ethernet"></i> 520 Technology: 521 </strong> 522 <span class="custom-layer-parameter-value"> 523 ${techDisplay} 524 </span> 525 </div> 526 527 <!-- Coverage --> 528 <div class="custom-layer-parameter"> 529 <strong class="custom-layer-parameter-label"> 530 <i class="fa-solid fa-percent"></i> 531 Coverage: 532 </strong> 533 <span class="custom-layer-parameter-value"> 534 ${coverageDisplay} 535 </span> 536 </div> 537 538 <!-- Version --> 539 <div class="custom-layer-parameter"> 540 <strong class="custom-layer-parameter-label"> 541 <i class="fa-regular fa-globe-pointer"></i> 542 Version: 543 </strong> 544 <span class="custom-layer-parameter-value"> 545 ${coverageVersion} 546 </span> 547 </div> 548 </div> 549 </div>`; 550 container.insertAdjacentHTML('afterbegin', layerHTML); 551 }); 552 553 if (maxLayers > activeCoverageLayers.length) { 554 addButton.style.display = '' 555 } else { 556 addButton.style.display = 'none' 557 } 558}
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.