| 1 | <html> |
|---|
| 2 | <head> |
|---|
| 3 | <meta charset="UTF-8"> |
|---|
| 4 | <title>CPTMS Camera Display Controller V1</title> |
|---|
| 5 | <style> |
|---|
| 6 | .toprow { |
|---|
| 7 | text-align: center |
|---|
| 8 | } |
|---|
| 9 | td { |
|---|
| 10 | border: 1px solid black; |
|---|
| 11 | } |
|---|
| 12 | img { /* confine the image to the cell dimensions */ |
|---|
| 13 | max-width: 100%; |
|---|
| 14 | max-height: 100%; |
|---|
| 15 | } |
|---|
| 16 | .caption { |
|---|
| 17 | font-family: sans-serif; |
|---|
| 18 | text-align: center |
|---|
| 19 | } |
|---|
| 20 | </style> |
|---|
| 21 | <script src="../common/js/fileutils.js"></script> |
|---|
| 22 | <script> |
|---|
| 23 | /* Camera Controller for CPTMS |
|---|
| 24 | * @author jdalbey Apr 2020 |
|---|
| 25 | */ |
|---|
| 26 | var kVDSstatusFile = "../dynamicdata/highway_status.json"; // dynamic json data file |
|---|
| 27 | var kCCTVfile = "../cptms/data_layers/cctv_locations_D12.gjson"; // CCTV locations |
|---|
| 28 | var vdsList; |
|---|
| 29 | var cctvList; |
|---|
| 30 | var routeCameras = {}; // for each route, a list of cameras on that route |
|---|
| 31 | var cameraDict = {}; // for each cameraid, the associated nearVDS and locationName properties |
|---|
| 32 | |
|---|
| 33 | // Extracts VDS and CCTV data and builds a lookup dictionary that maps route to camera id's and adds routes to drop down list |
|---|
| 34 | function init() |
|---|
| 35 | { |
|---|
| 36 | loadJSON(kVDSstatusFile, function(response) |
|---|
| 37 | { |
|---|
| 38 | // Parse JSON string into list of VDS's |
|---|
| 39 | vdsList = JSON.parse(response); |
|---|
| 40 | |
|---|
| 41 | }); |
|---|
| 42 | loadJSON(kCCTVfile, function(response) |
|---|
| 43 | { |
|---|
| 44 | // Parse JSON string into list of cctv's |
|---|
| 45 | cctvList = JSON.parse(response); |
|---|
| 46 | cctvList.features.forEach(buildDict); |
|---|
| 47 | // Build the route dropdowns using routeCameras keys |
|---|
| 48 | for (var quadrant = 1; quadrant <= 4; quadrant++) |
|---|
| 49 | { |
|---|
| 50 | routecombo = document.getElementById("R"+quadrant) |
|---|
| 51 | removeOptions(routecombo); |
|---|
| 52 | routecombo.add(document.createElement('option')); // an empty entry |
|---|
| 53 | // Get all the routes and sort them |
|---|
| 54 | var routeList = Object.keys(routeCameras); |
|---|
| 55 | routeList.sort() |
|---|
| 56 | // Add all the routes to the route dropdown box |
|---|
| 57 | for (var route of routeList) |
|---|
| 58 | { |
|---|
| 59 | opt1 = document.createElement('option') |
|---|
| 60 | opt1.text = opt1.value = route |
|---|
| 61 | routecombo.add(opt1); |
|---|
| 62 | } |
|---|
| 63 | } |
|---|
| 64 | }); |
|---|
| 65 | |
|---|
| 66 | // Start a timer to refresh the traffic colors every 30 seconds |
|---|
| 67 | var myTimer = setInterval(updateVDSlist, 30000); |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | // Takes in a CCTV location and builds a lookup dictionary that maps route to camera id's |
|---|
| 71 | function buildDict(cctvItem) |
|---|
| 72 | { |
|---|
| 73 | id = cctvItem.id |
|---|
| 74 | route = id.substring(3,6); // extract 3 character route number |
|---|
| 75 | if (route in routeCameras) |
|---|
| 76 | { |
|---|
| 77 | routeCameras[route].push(id); // add the camera id to the list for the route |
|---|
| 78 | } |
|---|
| 79 | else |
|---|
| 80 | { |
|---|
| 81 | routeCameras[route] = [id]; // special case for first occurrence of route |
|---|
| 82 | } |
|---|
| 83 | // Add a camera's info to the cameraDict |
|---|
| 84 | cameraDict[id] = {'nearVDS':cctvItem.properties["nearVDS"], |
|---|
| 85 | 'locationName':cctvItem.properties["locationName"]} |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | /* When a route is selected from the combobox, filter the |
|---|
| 89 | list of cameras for just those on that route. */ |
|---|
| 90 | function routechanged(routechoice, cameraselect) |
|---|
| 91 | { |
|---|
| 92 | // get the selected route |
|---|
| 93 | var e = document.getElementById(routechoice); |
|---|
| 94 | var currentRoute = e.options[e.selectedIndex].text; |
|---|
| 95 | // update the list of cameras |
|---|
| 96 | removeOptions(document.getElementById(cameraselect)); |
|---|
| 97 | fillOptions(currentRoute, cameraselect); |
|---|
| 98 | showView(cameraselect); //show default first image from camera dropdown for the selected route |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | // Helper: Remove all the options from a combo box |
|---|
| 102 | function removeOptions(selectbox) |
|---|
| 103 | { |
|---|
| 104 | var idx; |
|---|
| 105 | for(idx = selectbox.options.length - 1 ; idx >= 0 ; idx--) |
|---|
| 106 | { |
|---|
| 107 | selectbox.remove(idx); |
|---|
| 108 | } |
|---|
| 109 | } |
|---|
| 110 | |
|---|
| 111 | // Fill the selectbox with items from the lookup table that match route |
|---|
| 112 | // route param may be empty if the first item in the combo box was chosen |
|---|
| 113 | function fillOptions(route,cameraselect) |
|---|
| 114 | { |
|---|
| 115 | // IF the route is not empty |
|---|
| 116 | if (route.length > 0) |
|---|
| 117 | { |
|---|
| 118 | var cameracombo = document.getElementById(cameraselect) |
|---|
| 119 | // grab cameras from lookup table |
|---|
| 120 | cameranames = routeCameras[route]; |
|---|
| 121 | //console.log(cameranames.length + " cameras for route " + route); |
|---|
| 122 | // Create a new select OPTION for each camera |
|---|
| 123 | for (var idx = 0; idx < cameranames.length; idx++) |
|---|
| 124 | { |
|---|
| 125 | var opt1 = document.createElement('option') |
|---|
| 126 | opt1.value = cameranames[idx] |
|---|
| 127 | opt1.text = cameraDict[cameranames[idx]].locationName |
|---|
| 128 | cameracombo.add(opt1); |
|---|
| 129 | } |
|---|
| 130 | } |
|---|
| 131 | } |
|---|
| 132 | |
|---|
| 133 | // Display the image requested from a camera dropdown box |
|---|
| 134 | function showView(cameraselect) |
|---|
| 135 | { |
|---|
| 136 | var quadrant = ""; |
|---|
| 137 | // if route dropdown box is selected, then view image |
|---|
| 138 | if (typeof cameraselect === 'string') |
|---|
| 139 | { |
|---|
| 140 | quadrant = cameraselect.charAt(1) // extract numeric part of camera identifier |
|---|
| 141 | var chosenRoute = document.getElementById('R' + quadrant); |
|---|
| 142 | var imgElement = document.getElementById("img"+quadrant) |
|---|
| 143 | var e = document.getElementById(cameraselect); |
|---|
| 144 | // if no camera was selected |
|---|
| 145 | if (e.selectedIndex == -1) |
|---|
| 146 | { |
|---|
| 147 | imgElement.src="" // remove the image |
|---|
| 148 | } |
|---|
| 149 | else |
|---|
| 150 | { |
|---|
| 151 | var chosenCamera = e.options[e.selectedIndex].value; |
|---|
| 152 | nearvds = cameraDict[chosenCamera].nearVDS |
|---|
| 153 | // Search for the vds that is nearest |
|---|
| 154 | var idx = 0; |
|---|
| 155 | while (idx < vdsList.features.length && vdsList.features[idx].id != nearvds) |
|---|
| 156 | { |
|---|
| 157 | idx++; |
|---|
| 158 | } |
|---|
| 159 | // If we found the nearVDS |
|---|
| 160 | if (idx < vdsList.features.length) |
|---|
| 161 | { |
|---|
| 162 | // Obtain color and convert to speed |
|---|
| 163 | var foundVDS = vdsList.features[idx] |
|---|
| 164 | var color = foundVDS.properties['color'] |
|---|
| 165 | var speed = "freeflow" |
|---|
| 166 | if (color == "yellow") |
|---|
| 167 | { |
|---|
| 168 | speed = "slow" |
|---|
| 169 | } |
|---|
| 170 | if (color == "red") |
|---|
| 171 | { |
|---|
| 172 | speed = "stopped" |
|---|
| 173 | } |
|---|
| 174 | // construct filename |
|---|
| 175 | var filename = chosenCamera + "-day-"+speed+".jpg" |
|---|
| 176 | //console.log("Found " + foundVDS.id + " " + foundVDS.properties['color'] + " " + filename) |
|---|
| 177 | // Load the desired image |
|---|
| 178 | var preload = new Image(); |
|---|
| 179 | preload.onload = function() { |
|---|
| 180 | if (imgElement) |
|---|
| 181 | { |
|---|
| 182 | imgElement.src = preload.src; // image |
|---|
| 183 | imgElement.title=chosenCamera // tooltip |
|---|
| 184 | } |
|---|
| 185 | }; |
|---|
| 186 | // if couldn't load show as unavailable |
|---|
| 187 | preload.onerror = function() { |
|---|
| 188 | if (imgElement) { imgElement.src = "../cptms/images/CCTV/video_unavailable.jpg" } |
|---|
| 189 | }; |
|---|
| 190 | // attempt to load the image |
|---|
| 191 | preload.src = "../cptms/images/CCTV/"+filename; |
|---|
| 192 | //Reference: https://www.daniweb.com/programming/web-development/threads/272293/javascript-test-for-file-existence |
|---|
| 193 | } |
|---|
| 194 | } |
|---|
| 195 | } |
|---|
| 196 | //console.log("empty"); |
|---|
| 197 | } |
|---|
| 198 | // Execute periodically to reload the vds list with current data |
|---|
| 199 | function updateVDSlist() |
|---|
| 200 | { |
|---|
| 201 | loadJSON(kVDSstatusFile, function(response) |
|---|
| 202 | { |
|---|
| 203 | // Reload the vds list with current data |
|---|
| 204 | vdsList = JSON.parse(response); |
|---|
| 205 | // Refresh each camera view |
|---|
| 206 | var views = document.getElementsByClassName("camcombo"); |
|---|
| 207 | for (var quadrant of views) |
|---|
| 208 | { |
|---|
| 209 | //console.log(quadrant.id); |
|---|
| 210 | showView(quadrant.id); |
|---|
| 211 | } |
|---|
| 212 | }); |
|---|
| 213 | |
|---|
| 214 | } |
|---|
| 215 | </script> |
|---|
| 216 | </head> |
|---|
| 217 | <body onload="init()"> |
|---|
| 218 | <div style="text-align: center; width:80%"> |
|---|
| 219 | CPTMS Camera Controller |
|---|
| 220 | </div> |
|---|
| 221 | <table width="80%"> |
|---|
| 222 | <tr> |
|---|
| 223 | <td class="caption" width="50%"><img id="img1" src=""/><br> |
|---|
| 224 | Choose Route |
|---|
| 225 | <select id="R1" onchange='routechanged("R1","C1")'> |
|---|
| 226 | <option value="0"></option> |
|---|
| 227 | </select> |
|---|
| 228 | <br>Choose Camera |
|---|
| 229 | <select id="C1" class="camcombo" onchange='showView("C1")'> |
|---|
| 230 | </select></td> |
|---|
| 231 | <td class="caption"><img id="img2"src=""/><br> |
|---|
| 232 | Choose Route |
|---|
| 233 | <select id="R2" onchange='routechanged("R2","C2")'> |
|---|
| 234 | <option value="0"></option> |
|---|
| 235 | </select> |
|---|
| 236 | <br>Choose Camera |
|---|
| 237 | <select id="C2" class="camcombo" onchange='showView("C2")'> |
|---|
| 238 | </select></td> |
|---|
| 239 | </tr> |
|---|
| 240 | <tr> |
|---|
| 241 | <td class="caption"><img id="img3" src=""/><br> |
|---|
| 242 | Choose Route |
|---|
| 243 | <select id="R3" onchange='routechanged("R3","C3")'> |
|---|
| 244 | <option value="0"></option> |
|---|
| 245 | </select> |
|---|
| 246 | <br>Choose Camera |
|---|
| 247 | <select id="C3" class="camcombo" onchange='showView("C3")'> |
|---|
| 248 | </select> |
|---|
| 249 | </td> |
|---|
| 250 | <td class="caption"><img id="img4" src=""/><br> |
|---|
| 251 | Choose Route |
|---|
| 252 | <select id="R4" onchange='routechanged("R4","C4")'> |
|---|
| 253 | <option value="0"></option> |
|---|
| 254 | </select> |
|---|
| 255 | <br>Choose Camera |
|---|
| 256 | <select id="C4" class="camcombo" onchange='showView("C4")'> |
|---|
| 257 | </select> |
|---|
| 258 | </td> |
|---|
| 259 | </tr> |
|---|
| 260 | </table> |
|---|
| 261 | </body> |
|---|
| 262 | </html> |
|---|