├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── igcviewer.css ├── igcviewer.js ├── index.html ├── lib ├── jquery-2.1.3.min.js ├── jquery.flot.axislabels.js ├── jquery.flot.crosshair.js ├── jquery.flot.min.js ├── jquery.flot.resize.min.js ├── leaflet-awesome-markers │ ├── images │ │ ├── markers-matte.png │ │ ├── markers-matte@2x.png │ │ ├── markers-plain.png │ │ ├── markers-shadow.png │ │ ├── markers-shadow@2x.png │ │ ├── markers-soft.png │ │ └── markers-soft@2x.png │ ├── leaflet.awesome-markers.css │ └── leaflet.awesome-markers.min.js ├── leaflet │ ├── Semicircle.js │ ├── images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ └── marker-shadow.png │ ├── leaflet.css │ └── leaflet.js ├── moment-timezone-with-data.min.js └── moment.min.js ├── mapcontrol.js └── parseigc.js /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | The *JavaScript IGC Viewer* exists for the benefit of the whole gliding community. With your help, it can be made 4 | more powerful, more reliable, more elegant and hopefully more *useful* in the future. 5 | 6 | Even if you are not a programmer, then I would be grateful for any constructive feedback that you may have. 7 | 8 | Thanks in advance for your contributions! 9 | 10 | ## Bug reports, comments and suggestions 11 | 12 | If you find a bug, or would like to suggest a change, please check the [Issue Tracker](https://github.com/alistairmgreen/jsigc/issues) 13 | and add a ticket if none already exists. 14 | 15 | - Please apply a label to mark the issue as a bug, enhancement or question. 16 | - For bug reports, please indicate which browser you are using (e.g. Firefox / Chrome / Internet Explorer), 17 | *including the version number.* 18 | - If you have access to any other browsers, please try those as well. Does the bug affect all of them or is it 19 | specific to just one particular browser? 20 | - If the bug is triggered by a particular IGC file, then it would be very helpful to include a link 21 | to download a copy of that file. 22 | - For visual flaws, please include a screenshot if you can. 23 | 24 | ## Help with testing 25 | 26 | There are some important tests that I cannot do myself: 27 | 28 | ### Compatibility: 29 | - Does it work on tablet devices? 30 | - Does it work on Apple Macs? 31 | 32 | ### Time zones: 33 | 34 | IGC files always record the date and time as UTC. In countries with a large time offset from UTC, the 35 | flight may cross midnight and this has to be treated as a special case. However, all of the files 36 | that I have tried are from flights in the UK, so the code for dealing with mid-flight 37 | date changes is untested. 38 | 39 | ## If you are a programmer or web designer 40 | 41 | Code contributions are always welcome. If you are able to implement a new feature, fix a bug, or 42 | make some cosmetic improvements to the interface, then please feel free to: 43 | 44 | 1. Fork the repository. 45 | 2. Create a new branch with a descriptive name and implement your changes. 46 | 3. Check for any new commits on the master branch of the original repository. If there are any, 47 | then merge them into your branch or (preferably) rebase your changes on top of them. 48 | 4. Merge the changes into your `gh-pages` branch so that they will become visible on your 49 | copy of the IGC Viewer website. 50 | 5. Push your changes (both the feature branch and the `gh-pages` branch) to GitHub. 51 | 6. Send a pull request asking to merge your feature branch into my master branch. 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alistair Malcolm Green 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsIGC 2 | Glider pilots record their flights with electronic loggers which save GPS and barograph traces in International Gliding Commission 3 | (IGC) format. Recently, open source smartphone apps such as [XCSoar](http://www.xcsoar.org) have become available which 4 | are able to record an IGC flight log at a fraction of the cost of a dedicated logger, although these are not approved 5 | for use in competitions or badge claims. 6 | 7 | Unfortunately the most popular software for viewing IGC files on a PC is commercial and rather expensive. 8 | Although some free programs exist, they were mostly developed on Linux and installation on Windows PCs is non-trivial. 9 | Furthermore, some IGC viewers require a lot of screen space and can be difficult to use on laptops. 10 | 11 | *jsIGC* is an IGC viewer written in JavaScript and HTML 5, which is able to run in any modern Web browser. It draws 12 | the glider's flight path onto an interactive map, using [OpenStreetMap](http://www.openstreetmap.org) data, and also 13 | plots a graph of altitude against time. The responsive layout adjusts itself to fit any screen size, from a large 14 | widescreen monitor to a small laptop or even a smartphone. 15 | 16 | All processing takes place in client-side script, so there is no need to upload the IGC file (or anything else) 17 | to the server. 18 | 19 | ## Browser Support 20 | 21 | The browser must support the JavaScript FileReader API, which is used to open files from the local hard drive. 22 | This API is available in most modern browsers, but not in Internet Explorer 9 or earlier versions. 23 | 24 | jsIGC has been tested in the following browsers: 25 | * Firefox 37 (Windows and Android versions) 26 | * Internet Explorer 11 27 | * Chrome 42 for Android 28 | -------------------------------------------------------------------------------- /igcviewer.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 16px; 3 | font-family: sans-serif; 4 | } 5 | 6 | #map { 7 | width: 100%; 8 | height: calc(100% - 115px); 9 | margin-bottom: 15px; 10 | } 11 | 12 | #slider { 13 | height: 100px; 14 | } 15 | 16 | #slider label { 17 | width: 6em; 18 | vertical-align: top; 19 | } 20 | 21 | #slider button { 22 | width: 2em; 23 | vertical-align: top; 24 | } 25 | 26 | #slider input[type='range'] { 27 | width: calc(100% - 11em); 28 | padding-bottom: 0px; /* Default padding is huge in Internet Explorer.*/ 29 | } 30 | 31 | #mapWrapper, #barogram { 32 | max-height: 110vw; 33 | margin-top: 30px; 34 | margin-bottom: 30px; 35 | margin-left: auto; 36 | margin-right: auto; 37 | } 38 | 39 | #mapWrapper { 40 | height: 90vh; 41 | } 42 | 43 | #barogram { 44 | cursor: crosshair; 45 | } 46 | 47 | @media print, screen and (max-width: 1100px) { 48 | #mapWrapper, #barogram { 49 | width: 90%; 50 | display: inline-block; 51 | position: relative; 52 | } 53 | 54 | #barogram { 55 | height: calc(90vh - 130px); 56 | } 57 | } 58 | 59 | @media screen and (min-width: 1100px) { 60 | #mapWrapper, #barogram { 61 | display: inline-block; 62 | width: calc(50% - 30px); 63 | } 64 | 65 | #mapWrapper { 66 | margin-left: 30px; 67 | } 68 | 69 | #barogram { 70 | position: absolute; 71 | right: 0px; 72 | height: 90vh; 73 | } 74 | } 75 | 76 | #errorMessage { 77 | font-weight: bold; 78 | color: Red; 79 | margin-top: 30px; 80 | margin-bottom: 30px; 81 | } 82 | 83 | #igcFileDisplay { 84 | display: none; 85 | } 86 | 87 | table#headerInfo { 88 | border-spacing: 1em 0; 89 | } 90 | 91 | #headerInfo th { 92 | text-align: left; 93 | font-weight: bold; 94 | } 95 | -------------------------------------------------------------------------------- /igcviewer.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 'use strict'; 3 | 4 | var igcFile = null; 5 | var barogramPlot = null; 6 | var altitudeConversionFactor = 1.0; // Conversion from metres to required units 7 | 8 | function positionDisplay(position) { 9 | function toDegMins(degreevalue) { 10 | var wholedegrees= Math.floor(degreevalue); 11 | var minutevalue = (60*(degreevalue-wholedegrees)).toFixed(3); 12 | return wholedegrees + '\u00B0\u00A0' + minutevalue + '\u00B4'; 13 | } 14 | 15 | var positionLatitude= toDegMins(Math.abs(position[0])); 16 | var positionLongitude=toDegMins(Math.abs(position[1])); 17 | if(position[0] > 0) { 18 | positionLatitude += "N"; 19 | } 20 | else { 21 | positionLatitude += "S"; 22 | } 23 | if(position[1] > 0) { 24 | positionLongitude += "E"; 25 | } 26 | else { 27 | positionLongitude += "W"; 28 | } 29 | return positionLatitude + ", " + positionLongitude; 30 | } 31 | 32 | function pad(n) { 33 | return (n < 10) ? ("0" + n.toString()) : n.toString(); 34 | } 35 | 36 | function plotBarogram() { 37 | var nPoints = igcFile.recordTime.length; 38 | var pressureBarogramData = []; 39 | var gpsBarogramData = []; 40 | var j; 41 | var timestamp; 42 | 43 | for (j = 0; j < nPoints; j++) { 44 | timestamp = igcFile.recordTime[j].getTime(); 45 | pressureBarogramData.push([timestamp, igcFile.pressureAltitude[j] * altitudeConversionFactor]); 46 | gpsBarogramData.push([timestamp, igcFile.gpsAltitude[j] * altitudeConversionFactor]); 47 | } 48 | 49 | var baro = $.plot($('#barogram'), [{ 50 | label: 'Pressure altitude', 51 | data: pressureBarogramData 52 | }, { 53 | label: 'GPS altitude', 54 | data: gpsBarogramData 55 | }], { 56 | axisLabels: { 57 | show: true 58 | }, 59 | xaxis: { 60 | axisLabel: 'Time', 61 | tickFormatter: function (t, axis) { 62 | return moment(t).format('HH:mm'); 63 | }, 64 | ticks: function (axis) { 65 | var ticks = []; 66 | var startMoment = moment(axis.min); 67 | var endMoment = moment(axis.max); 68 | var durationMinutes = endMoment.diff(startMoment, 'minutes'); 69 | var interval; 70 | if (durationMinutes <= 10) { 71 | interval = 1; 72 | } 73 | if (durationMinutes <= 50) { 74 | interval = 5; 75 | } 76 | else if (durationMinutes <= 100) { 77 | interval = 10; 78 | } 79 | else if (durationMinutes <= 150) { 80 | interval = 15; 81 | } 82 | else if (durationMinutes <= 300) { 83 | interval = 30; 84 | } 85 | else if (durationMinutes <= 600) { 86 | interval = 60; 87 | } 88 | else { 89 | interval = 120; 90 | } 91 | 92 | var tick = startMoment.clone(); 93 | tick.minutes(0).seconds(0); 94 | while (tick < endMoment) { 95 | if (tick > startMoment) { 96 | ticks.push(tick.valueOf()); 97 | } 98 | tick.add(interval, 'minutes'); 99 | } 100 | 101 | return ticks; 102 | } 103 | }, 104 | yaxis: { 105 | axisLabel: 'Altitude / ' + $('#altitudeUnits').val() 106 | }, 107 | 108 | crosshair: { 109 | mode: 'xy' 110 | }, 111 | 112 | grid: { 113 | clickable: true, 114 | autoHighlight: false 115 | } 116 | }); 117 | 118 | return baro; 119 | } 120 | 121 | function updateTimeline (timeIndex, mapControl) { 122 | var currentPosition = igcFile.latLong[timeIndex]; 123 | var positionText=positionDisplay(currentPosition); 124 | var unitName = $('#altitudeUnits').val(); 125 | $('#timePositionDisplay').text( 126 | moment(igcFile.recordTime[timeIndex]).format('HH:mm:ss') + ': ' + 127 | (igcFile.pressureAltitude[timeIndex] * altitudeConversionFactor).toFixed(0) + ' ' + 128 | unitName + ' (barometric) / ' + 129 | (igcFile.gpsAltitude[timeIndex] * altitudeConversionFactor).toFixed(0) + ' ' + 130 | unitName + ' (GPS); ' + 131 | positionText 132 | ); 133 | 134 | mapControl.setTimeMarker(timeIndex); 135 | 136 | barogramPlot.lockCrosshair({ 137 | x: igcFile.recordTime[timeIndex].getTime(), 138 | y: igcFile.pressureAltitude[timeIndex] * altitudeConversionFactor 139 | }); 140 | } 141 | 142 | function displayIgc(mapControl) { 143 | // Display the headers. 144 | var displayDate = moment(igcFile.recordTime[0]).format('LL'); 145 | var headerTable = $('#headerInfo tbody'); 146 | headerTable.html('') 147 | .append( 148 | $('').append($('').text('Date')) 149 | .append($('').text(displayDate)) 150 | ); 151 | var headerName; 152 | var headerIndex; 153 | for (headerIndex = 0; headerIndex < igcFile.headers.length; headerIndex++) { 154 | headerTable.append( 155 | $('').append($('').text(igcFile.headers[headerIndex].name)) 156 | .append($('').text(igcFile.headers[headerIndex].value)) 157 | ); 158 | } 159 | 160 | // Show the task declaration if it is present. 161 | 162 | if (igcFile.task.coordinates.length > 0) { 163 | //eliminate anything with empty start line coordinates 164 | if(igcFile.task.coordinates[0][0] !==0) { 165 | $('#task').show(); 166 | var taskList = $('#task ul').first().html(''); 167 | var j; 168 | //Now add TP numbers. Change to unordered list 169 | if(igcFile.task.takeoff.length > 0) { 170 | taskList.append($('
  • ').text("Takeoff: " + igcFile.task.takeoff)); 171 | } 172 | for (j =0; j ').text("Start: " + igcFile.task.names[j])); 176 | break; 177 | case ( igcFile.task.names.length-1): 178 | taskList.append($('
  • ').text("Finish: " + igcFile.task.names[j])); 179 | break; 180 | default: 181 | taskList.append($('
  • ').text("TP" + (j).toString() + ": " + igcFile.task.names[j])); 182 | } 183 | } 184 | if(igcFile.task.landing.length > 0) { 185 | taskList.append($('
  • ').text("Landing: : " + igcFile.task.landing)); 186 | } 187 | mapControl.addTask(igcFile.task.coordinates, igcFile.task.names); 188 | } 189 | } 190 | else { 191 | $('#task').hide(); 192 | } 193 | 194 | // Reveal the map and graph. We have to do this before 195 | // setting the zoom level of the map or plotting the graph. 196 | $('#igcFileDisplay').show(); 197 | 198 | mapControl.addTrack(igcFile.latLong); 199 | barogramPlot = plotBarogram(igcFile); 200 | 201 | $('#timeSlider').prop('max', igcFile.recordTime.length - 1); 202 | updateTimeline(0, mapControl); 203 | } 204 | 205 | function storePreference(name, value) { 206 | if (window.localStorage) { 207 | try { 208 | localStorage.setItem(name, value); 209 | } 210 | catch (e) { 211 | // If permission is denied, ignore the error. 212 | } 213 | } 214 | } 215 | 216 | $(document).ready(function () { 217 | var mapControl = createMapControl('map'); 218 | 219 | var timeZoneSelect = $('#timeZoneSelect'); 220 | $.each(moment.tz.names(), function(index, name) { 221 | timeZoneSelect.append( 222 | $('', { value: name }).text(name)); 223 | }); 224 | 225 | timeZoneSelect.change(function () { 226 | var selectedZone = $(this).val(); 227 | moment.tz.setDefault(selectedZone); 228 | if (igcFile !== null) { 229 | barogramPlot = plotBarogram(); 230 | updateTimeline($('#timeSlider').val(), mapControl); 231 | $('#headerInfo td').first().text(moment(igcFile.recordTime[0]).format('LL')); 232 | } 233 | 234 | storePreference('timeZone', selectedZone); 235 | }); 236 | 237 | $('#fileControl').change(function () { 238 | if (this.files.length > 0) { 239 | var reader = new FileReader(); 240 | reader.onload = function(e) { 241 | try { 242 | $('#errorMessage').text(''); 243 | mapControl.reset(); 244 | $('#timeSlider').val(0); 245 | 246 | igcFile = parseIGC(this.result); 247 | displayIgc(mapControl); 248 | } catch (ex) { 249 | if (ex instanceof IGCException) { 250 | $('#errorMessage').text(ex.message); 251 | } 252 | else { 253 | throw ex; 254 | } 255 | } 256 | }; 257 | reader.readAsText(this.files[0]); 258 | } 259 | }); 260 | 261 | $('#altitudeUnits').change(function (e, raisedProgrammatically) { 262 | var altitudeUnit = $(this).val(); 263 | if (altitudeUnit === 'feet') { 264 | altitudeConversionFactor = 3.2808399; 265 | } 266 | else { 267 | altitudeConversionFactor = 1.0; 268 | } 269 | 270 | if (igcFile !== null) { 271 | barogramPlot = plotBarogram(); 272 | updateTimeline($('#timeSlider').val(), mapControl); 273 | } 274 | 275 | if (!raisedProgrammatically) { 276 | storePreference("altitudeUnit", altitudeUnit); 277 | } 278 | }); 279 | 280 | // We need to handle the 'change' event for IE, but 281 | // 'input' for Chrome and Firefox in order to update smoothly 282 | // as the range input is dragged. 283 | $('#timeSlider').on('input', function() { 284 | var t = parseInt($(this).val(), 10); 285 | updateTimeline(t, mapControl); 286 | }); 287 | $('#timeSlider').on('change', function() { 288 | var t = parseInt($(this).val(), 10); 289 | updateTimeline(t, mapControl); 290 | }); 291 | 292 | $('#timeBack').click(function() { 293 | var slider = $('#timeSlider'); 294 | var curTime = parseInt(slider.val(), 10); 295 | curTime--; 296 | if(curTime < 0) { 297 | curTime = 0; 298 | } 299 | slider.val(curTime); 300 | updateTimeline(curTime, mapControl); 301 | }); 302 | 303 | $('#timeForward').click(function() { 304 | var slider = $('#timeSlider'); 305 | var curTime = parseInt(slider.val(), 10); 306 | var maxval= slider.prop('max'); 307 | curTime++; 308 | if(curTime > maxval) { 309 | curTime = maxval; 310 | } 311 | slider.val(curTime); 312 | updateTimeline(curTime, mapControl); 313 | }); 314 | 315 | $('#barogram').on('plotclick', function (event, pos, item) { 316 | console.log('plot click'); 317 | if (item) { 318 | updateTimeline(item.dataIndex, mapControl); 319 | $('#timeSlider').val(item.dataIndex); 320 | } 321 | }); 322 | 323 | // Load preferences from local storage, if available. 324 | 325 | var altitudeUnit = ''; 326 | var timeZone = ''; 327 | 328 | if (window.localStorage) { 329 | try { 330 | altitudeUnit = localStorage.getItem('altitudeUnit'); 331 | if (altitudeUnit) { 332 | $('#altitudeUnits').val(altitudeUnit).trigger('change', true); 333 | } 334 | 335 | timeZone = localStorage.getItem('timeZone'); 336 | } 337 | catch (e) { 338 | // If permission is denied, ignore the error. 339 | } 340 | } 341 | 342 | if (!timeZone) { 343 | timeZone = 'UTC'; 344 | } 345 | 346 | timeZoneSelect.val(timeZone); 347 | moment.tz.setDefault(timeZone); 348 | }); 349 | }(jQuery)); 350 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | IGC Viewer 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

    JavaScript IGC Viewer

    16 | 17 |

    18 | A free browser-based tool for viewing GPS tracks and barograph traces from gliding loggers. The files must be in the International 19 | Gliding Commission (IGC) format. 20 |

    21 | 22 |

    23 | 24 | An improved version of this application is now available 25 | at the British Gliding Association Ladder website. 26 | 27 |

    28 | 29 |

    © 2015-2016 Alistair Malcolm Green and Richard Brisbourne

    30 | 31 | 36 | 37 | 38 | 39 | 40 |

    41 | Note: The file will be processed entirely within your web browser. Nothing will be uploaded to the Internet. 42 |

    43 | 44 |
    45 |
    46 | 47 |

    Preferences

    48 |

    49 | 50 | 54 |

    55 | 56 |

    57 | 58 | 60 |

    61 | 62 |
    63 |

    Flight Information

    64 | 65 | 66 | 67 |
    68 | 69 |
    70 |

    Task

    71 |
      72 |
    73 |
    74 | 75 |
    76 |
    77 |
    78 | 79 | 80 | 81 | 82 |

    83 |
    84 |
    85 |
    86 |
    87 | 88 |

    Credits

    89 | 90 |

    91 | This application was made possible by the following free software projects: 92 |

    101 |

    102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /lib/jquery.flot.axislabels.js: -------------------------------------------------------------------------------- 1 | /* 2 | Axis Labels Plugin for flot. 3 | http://github.com/markrcote/flot-axislabels 4 | 5 | Original code is Copyright (c) 2010 Xuan Luo. 6 | Original code was released under the GPLv3 license by Xuan Luo, September 2010. 7 | Original code was rereleased under the MIT license by Xuan Luo, April 2012. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | (function ($) { 30 | var options = { 31 | axisLabels: { 32 | show: true 33 | } 34 | }; 35 | 36 | function canvasSupported() { 37 | return !!document.createElement('canvas').getContext; 38 | } 39 | 40 | function canvasTextSupported() { 41 | if (!canvasSupported()) { 42 | return false; 43 | } 44 | var dummy_canvas = document.createElement('canvas'); 45 | var context = dummy_canvas.getContext('2d'); 46 | return typeof context.fillText == 'function'; 47 | } 48 | 49 | function css3TransitionSupported() { 50 | var div = document.createElement('div'); 51 | return typeof div.style.MozTransition != 'undefined' // Gecko 52 | || typeof div.style.OTransition != 'undefined' // Opera 53 | || typeof div.style.webkitTransition != 'undefined' // WebKit 54 | || typeof div.style.transition != 'undefined'; 55 | } 56 | 57 | 58 | function AxisLabel(axisName, position, padding, plot, opts) { 59 | this.axisName = axisName; 60 | this.position = position; 61 | this.padding = padding; 62 | this.plot = plot; 63 | this.opts = opts; 64 | this.width = 0; 65 | this.height = 0; 66 | } 67 | 68 | AxisLabel.prototype.cleanup = function() { 69 | }; 70 | 71 | 72 | CanvasAxisLabel.prototype = new AxisLabel(); 73 | CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; 74 | function CanvasAxisLabel(axisName, position, padding, plot, opts) { 75 | AxisLabel.prototype.constructor.call(this, axisName, position, padding, 76 | plot, opts); 77 | } 78 | 79 | CanvasAxisLabel.prototype.calculateSize = function() { 80 | if (!this.opts.axisLabelFontSizePixels) 81 | this.opts.axisLabelFontSizePixels = 14; 82 | if (!this.opts.axisLabelFontFamily) 83 | this.opts.axisLabelFontFamily = 'sans-serif'; 84 | 85 | var textWidth = this.opts.axisLabelFontSizePixels + this.padding; 86 | var textHeight = this.opts.axisLabelFontSizePixels + this.padding; 87 | if (this.position == 'left' || this.position == 'right') { 88 | this.width = this.opts.axisLabelFontSizePixels + this.padding; 89 | this.height = 0; 90 | } else { 91 | this.width = 0; 92 | this.height = this.opts.axisLabelFontSizePixels + this.padding; 93 | } 94 | }; 95 | 96 | CanvasAxisLabel.prototype.draw = function(box) { 97 | if (!this.opts.axisLabelColour) 98 | this.opts.axisLabelColour = 'black'; 99 | var ctx = this.plot.getCanvas().getContext('2d'); 100 | ctx.save(); 101 | ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + 102 | this.opts.axisLabelFontFamily; 103 | ctx.fillStyle = this.opts.axisLabelColour; 104 | var width = ctx.measureText(this.opts.axisLabel).width; 105 | var height = this.opts.axisLabelFontSizePixels; 106 | var x, y, angle = 0; 107 | if (this.position == 'top') { 108 | x = box.left + box.width/2 - width/2; 109 | y = box.top + height*0.72; 110 | } else if (this.position == 'bottom') { 111 | x = box.left + box.width/2 - width/2; 112 | y = box.top + box.height - height*0.72; 113 | } else if (this.position == 'left') { 114 | x = box.left + height*0.72; 115 | y = box.height/2 + box.top + width/2; 116 | angle = -Math.PI/2; 117 | } else if (this.position == 'right') { 118 | x = box.left + box.width - height*0.72; 119 | y = box.height/2 + box.top - width/2; 120 | angle = Math.PI/2; 121 | } 122 | ctx.translate(x, y); 123 | ctx.rotate(angle); 124 | ctx.fillText(this.opts.axisLabel, 0, 0); 125 | ctx.restore(); 126 | }; 127 | 128 | 129 | HtmlAxisLabel.prototype = new AxisLabel(); 130 | HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; 131 | function HtmlAxisLabel(axisName, position, padding, plot, opts) { 132 | AxisLabel.prototype.constructor.call(this, axisName, position, 133 | padding, plot, opts); 134 | this.elem = null; 135 | } 136 | 137 | HtmlAxisLabel.prototype.calculateSize = function() { 138 | var elem = $('
    ' + 139 | this.opts.axisLabel + '
    '); 140 | this.plot.getPlaceholder().append(elem); 141 | // store height and width of label itself, for use in draw() 142 | this.labelWidth = elem.outerWidth(true); 143 | this.labelHeight = elem.outerHeight(true); 144 | elem.remove(); 145 | 146 | this.width = this.height = 0; 147 | if (this.position == 'left' || this.position == 'right') { 148 | this.width = this.labelWidth + this.padding; 149 | } else { 150 | this.height = this.labelHeight + this.padding; 151 | } 152 | }; 153 | 154 | HtmlAxisLabel.prototype.cleanup = function() { 155 | if (this.elem) { 156 | this.elem.remove(); 157 | } 158 | }; 159 | 160 | HtmlAxisLabel.prototype.draw = function(box) { 161 | this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); 162 | this.elem = $('
    ' 164 | + this.opts.axisLabel + '
    '); 165 | this.plot.getPlaceholder().append(this.elem); 166 | if (this.position == 'top') { 167 | this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 168 | 'px'); 169 | this.elem.css('top', box.top + 'px'); 170 | } else if (this.position == 'bottom') { 171 | this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 172 | 'px'); 173 | this.elem.css('top', box.top + box.height - this.labelHeight + 174 | 'px'); 175 | } else if (this.position == 'left') { 176 | this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 177 | 'px'); 178 | this.elem.css('left', box.left + 'px'); 179 | } else if (this.position == 'right') { 180 | this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 181 | 'px'); 182 | this.elem.css('left', box.left + box.width - this.labelWidth + 183 | 'px'); 184 | } 185 | }; 186 | 187 | 188 | CssTransformAxisLabel.prototype = new HtmlAxisLabel(); 189 | CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; 190 | function CssTransformAxisLabel(axisName, position, padding, plot, opts) { 191 | HtmlAxisLabel.prototype.constructor.call(this, axisName, position, 192 | padding, plot, opts); 193 | } 194 | 195 | CssTransformAxisLabel.prototype.calculateSize = function() { 196 | HtmlAxisLabel.prototype.calculateSize.call(this); 197 | this.width = this.height = 0; 198 | if (this.position == 'left' || this.position == 'right') { 199 | this.width = this.labelHeight + this.padding; 200 | } else { 201 | this.height = this.labelHeight + this.padding; 202 | } 203 | }; 204 | 205 | CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { 206 | var stransforms = { 207 | '-moz-transform': '', 208 | '-webkit-transform': '', 209 | '-o-transform': '', 210 | '-ms-transform': '' 211 | }; 212 | if (x != 0 || y != 0) { 213 | var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; 214 | stransforms['-moz-transform'] += stdTranslate; 215 | stransforms['-webkit-transform'] += stdTranslate; 216 | stransforms['-o-transform'] += stdTranslate; 217 | stransforms['-ms-transform'] += stdTranslate; 218 | } 219 | if (degrees != 0) { 220 | var rotation = degrees / 90; 221 | var stdRotate = ' rotate(' + degrees + 'deg)'; 222 | stransforms['-moz-transform'] += stdRotate; 223 | stransforms['-webkit-transform'] += stdRotate; 224 | stransforms['-o-transform'] += stdRotate; 225 | stransforms['-ms-transform'] += stdRotate; 226 | } 227 | var s = 'top: 0; left: 0; '; 228 | for (var prop in stransforms) { 229 | if (stransforms[prop]) { 230 | s += prop + ':' + stransforms[prop] + ';'; 231 | } 232 | } 233 | s += ';'; 234 | return s; 235 | }; 236 | 237 | CssTransformAxisLabel.prototype.calculateOffsets = function(box) { 238 | var offsets = { x: 0, y: 0, degrees: 0 }; 239 | if (this.position == 'bottom') { 240 | offsets.x = box.left + box.width/2 - this.labelWidth/2; 241 | offsets.y = box.top + box.height - this.labelHeight; 242 | } else if (this.position == 'top') { 243 | offsets.x = box.left + box.width/2 - this.labelWidth/2; 244 | offsets.y = box.top; 245 | } else if (this.position == 'left') { 246 | offsets.degrees = -90; 247 | offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; 248 | offsets.y = box.height/2 + box.top; 249 | } else if (this.position == 'right') { 250 | offsets.degrees = 90; 251 | offsets.x = box.left + box.width - this.labelWidth/2 252 | - this.labelHeight/2; 253 | offsets.y = box.height/2 + box.top; 254 | } 255 | offsets.x = Math.round(offsets.x); 256 | offsets.y = Math.round(offsets.y); 257 | 258 | return offsets; 259 | }; 260 | 261 | CssTransformAxisLabel.prototype.draw = function(box) { 262 | this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); 263 | var offsets = this.calculateOffsets(box); 264 | this.elem = $('
    ' + this.opts.axisLabel + '
    '); 268 | this.plot.getPlaceholder().append(this.elem); 269 | }; 270 | 271 | 272 | IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); 273 | IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; 274 | function IeTransformAxisLabel(axisName, position, padding, plot, opts) { 275 | CssTransformAxisLabel.prototype.constructor.call(this, axisName, 276 | position, padding, 277 | plot, opts); 278 | this.requiresResize = false; 279 | } 280 | 281 | IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { 282 | // I didn't feel like learning the crazy Matrix stuff, so this uses 283 | // a combination of the rotation transform and CSS positioning. 284 | var s = ''; 285 | if (degrees != 0) { 286 | var rotation = degrees/90; 287 | while (rotation < 0) { 288 | rotation += 4; 289 | } 290 | s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; 291 | // see below 292 | this.requiresResize = (this.position == 'right'); 293 | } 294 | if (x != 0) { 295 | s += 'left: ' + x + 'px; '; 296 | } 297 | if (y != 0) { 298 | s += 'top: ' + y + 'px; '; 299 | } 300 | return s; 301 | }; 302 | 303 | IeTransformAxisLabel.prototype.calculateOffsets = function(box) { 304 | var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( 305 | this, box); 306 | // adjust some values to take into account differences between 307 | // CSS and IE rotations. 308 | if (this.position == 'top') { 309 | // FIXME: not sure why, but placing this exactly at the top causes 310 | // the top axis label to flip to the bottom... 311 | offsets.y = box.top + 1; 312 | } else if (this.position == 'left') { 313 | offsets.x = box.left; 314 | offsets.y = box.height/2 + box.top - this.labelWidth/2; 315 | } else if (this.position == 'right') { 316 | offsets.x = box.left + box.width - this.labelHeight; 317 | offsets.y = box.height/2 + box.top - this.labelWidth/2; 318 | } 319 | return offsets; 320 | }; 321 | 322 | IeTransformAxisLabel.prototype.draw = function(box) { 323 | CssTransformAxisLabel.prototype.draw.call(this, box); 324 | if (this.requiresResize) { 325 | this.elem = this.plot.getPlaceholder().find("." + this.axisName + 326 | "Label"); 327 | // Since we used CSS positioning instead of transforms for 328 | // translating the element, and since the positioning is done 329 | // before any rotations, we have to reset the width and height 330 | // in case the browser wrapped the text (specifically for the 331 | // y2axis). 332 | this.elem.css('width', this.labelWidth); 333 | this.elem.css('height', this.labelHeight); 334 | } 335 | }; 336 | 337 | 338 | function init(plot) { 339 | plot.hooks.processOptions.push(function (plot, options) { 340 | 341 | if (!options.axisLabels.show) 342 | return; 343 | 344 | // This is kind of a hack. There are no hooks in Flot between 345 | // the creation and measuring of the ticks (setTicks, measureTickLabels 346 | // in setupGrid() ) and the drawing of the ticks and plot box 347 | // (insertAxisLabels in setupGrid() ). 348 | // 349 | // Therefore, we use a trick where we run the draw routine twice: 350 | // the first time to get the tick measurements, so that we can change 351 | // them, and then have it draw it again. 352 | var secondPass = false; 353 | 354 | var axisLabels = {}; 355 | var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; 356 | 357 | var defaultPadding = 2; // padding between axis and tick labels 358 | plot.hooks.draw.push(function (plot, ctx) { 359 | var hasAxisLabels = false; 360 | if (!secondPass) { 361 | // MEASURE AND SET OPTIONS 362 | $.each(plot.getAxes(), function(axisName, axis) { 363 | var opts = axis.options // Flot 0.7 364 | || plot.getOptions()[axisName]; // Flot 0.6 365 | 366 | // Handle redraws initiated outside of this plug-in. 367 | if (axisName in axisLabels) { 368 | axis.labelHeight = axis.labelHeight - 369 | axisLabels[axisName].height; 370 | axis.labelWidth = axis.labelWidth - 371 | axisLabels[axisName].width; 372 | opts.labelHeight = axis.labelHeight; 373 | opts.labelWidth = axis.labelWidth; 374 | axisLabels[axisName].cleanup(); 375 | delete axisLabels[axisName]; 376 | } 377 | 378 | if (!opts || !opts.axisLabel || !axis.show) 379 | return; 380 | 381 | hasAxisLabels = true; 382 | var renderer = null; 383 | 384 | if (!opts.axisLabelUseHtml && 385 | navigator.appName == 'Microsoft Internet Explorer') { 386 | var ua = navigator.userAgent; 387 | var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 388 | if (re.exec(ua) != null) { 389 | rv = parseFloat(RegExp.$1); 390 | } 391 | if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { 392 | renderer = CssTransformAxisLabel; 393 | } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { 394 | renderer = IeTransformAxisLabel; 395 | } else if (opts.axisLabelUseCanvas) { 396 | renderer = CanvasAxisLabel; 397 | } else { 398 | renderer = HtmlAxisLabel; 399 | } 400 | } else { 401 | if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { 402 | renderer = HtmlAxisLabel; 403 | } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { 404 | renderer = CanvasAxisLabel; 405 | } else { 406 | renderer = CssTransformAxisLabel; 407 | } 408 | } 409 | 410 | var padding = opts.axisLabelPadding === undefined ? 411 | defaultPadding : opts.axisLabelPadding; 412 | 413 | axisLabels[axisName] = new renderer(axisName, 414 | axis.position, padding, 415 | plot, opts); 416 | 417 | // flot interprets axis.labelHeight and .labelWidth as 418 | // the height and width of the tick labels. We increase 419 | // these values to make room for the axis label and 420 | // padding. 421 | 422 | axisLabels[axisName].calculateSize(); 423 | 424 | // AxisLabel.height and .width are the size of the 425 | // axis label and padding. 426 | // Just set opts here because axis will be sorted out on 427 | // the redraw. 428 | 429 | opts.labelHeight = axis.labelHeight + 430 | axisLabels[axisName].height; 431 | opts.labelWidth = axis.labelWidth + 432 | axisLabels[axisName].width; 433 | }); 434 | 435 | // If there are axis labels, re-draw with new label widths and 436 | // heights. 437 | 438 | if (hasAxisLabels) { 439 | secondPass = true; 440 | plot.setupGrid(); 441 | plot.draw(); 442 | } 443 | } else { 444 | secondPass = false; 445 | // DRAW 446 | $.each(plot.getAxes(), function(axisName, axis) { 447 | var opts = axis.options // Flot 0.7 448 | || plot.getOptions()[axisName]; // Flot 0.6 449 | if (!opts || !opts.axisLabel || !axis.show) 450 | return; 451 | 452 | axisLabels[axisName].draw(axis.box); 453 | }); 454 | } 455 | }); 456 | }); 457 | } 458 | 459 | 460 | $.plot.plugins.push({ 461 | init: init, 462 | options: options, 463 | name: 'axisLabels', 464 | version: '2.0' 465 | }); 466 | })(jQuery); 467 | -------------------------------------------------------------------------------- /lib/jquery.flot.crosshair.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for showing crosshairs on the plot. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen, 4 | 2015 Alistair Green. 5 | Licensed under the MIT license. 6 | 7 | The plugin supports these options: 8 | 9 | crosshair: { 10 | mode: null or "x" or "y" or "xy" 11 | color: color 12 | lineWidth: number 13 | } 14 | 15 | Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical 16 | crosshair that lets you trace the values on the x axis, "y" enables a 17 | horizontal crosshair and "xy" enables them both. "color" is the color of the 18 | crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of 19 | the drawn lines (default is 1). 20 | 21 | The plugin also adds one public method: 22 | 23 | - lockCrosshair( pos ) 24 | 25 | Set the position of the crosshair. 26 | "pos" is in coordinates of the plot and should be on the 27 | form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple 28 | axes), which is coincidentally the same format as what you get from a 29 | "plothover" event. If "pos" is null, the crosshair is cleared. 30 | 31 | */ 32 | 33 | (function ($) { 34 | 'use strict'; 35 | 36 | var options = { 37 | crosshair: { 38 | mode: null, // one of null, "x", "y" or "xy", 39 | color: "rgba(170, 0, 0, 0.80)", 40 | lineWidth: 1 41 | } 42 | }; 43 | 44 | function init(plot) { 45 | // position of crosshair in axis units 46 | var crosshair = { x: null, y: null }; 47 | 48 | plot.lockCrosshair = function lockCrosshair(pos) { 49 | if (pos) { 50 | crosshair.x = pos.x; 51 | crosshair.y = pos.y; 52 | } 53 | else { 54 | crosshair.x = null; 55 | crosshair.y = null; 56 | } 57 | 58 | plot.triggerRedrawOverlay(); 59 | }; 60 | 61 | plot.hooks.drawOverlay.push(function (plot, ctx) { 62 | var c = plot.getOptions().crosshair; 63 | if (!c.mode) { 64 | return; 65 | } 66 | 67 | var plotOffset = plot.getPlotOffset(); 68 | 69 | ctx.save(); 70 | ctx.translate(plotOffset.left, plotOffset.top); 71 | 72 | if (crosshair.x !== null && crosshair.y !== null) { 73 | // Convert logical position to pixels. Have to do this 74 | // at draw time to cope with resizing. 75 | var o = plot.p2c(crosshair); 76 | var pixelPos = { 77 | x: Math.max(0, Math.min(o.left, plot.width())), 78 | y: Math.max(0, Math.min(o.top, plot.height())) 79 | }; 80 | 81 | var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; 82 | 83 | ctx.strokeStyle = c.color; 84 | ctx.lineWidth = c.lineWidth; 85 | ctx.lineJoin = "round"; 86 | 87 | ctx.beginPath(); 88 | if (c.mode.indexOf("x") !== -1) { 89 | var drawX = Math.floor(pixelPos.x) + adj; 90 | ctx.moveTo(drawX, 0); 91 | ctx.lineTo(drawX, plot.height()); 92 | } 93 | if (c.mode.indexOf("y") !== -1) { 94 | var drawY = Math.floor(pixelPos.y) + adj; 95 | ctx.moveTo(0, drawY); 96 | ctx.lineTo(plot.width(), drawY); 97 | } 98 | ctx.stroke(); 99 | } 100 | ctx.restore(); 101 | }); 102 | } 103 | 104 | $.plot.plugins.push({ 105 | init: init, 106 | options: options, 107 | name: 'crosshair', 108 | version: '1.1' 109 | }); 110 | }(jQuery)); 111 | -------------------------------------------------------------------------------- /lib/jquery.flot.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;if(!$.fn.detach){$.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){var cache=this._textCache;for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layer=this.getTextLayer(layerKey),layerCache=cache[layerKey];layer.hide();for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){if(position.active){if(!position.rendered){layer.append(position.element);position.rendered=true}}else{positions.splice(i--,1);if(position.rendered){position.element.detach()}}}if(positions.length==0){delete styleCache[key]}}}}}layer.show()}}};Canvas.prototype.getTextLayer=function(classes){var layer=this.text[classes];if(layer==null){if(this.textContainer==null){this.textContainer=$("
    ").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)}layer=this.text[classes]=$("
    ").addClass(classes).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)}return layer};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px/"+font.lineHeight+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var element=$("
    ").html(text).css({position:"absolute","max-width":width,top:-9999}).appendTo(this.getTextLayer(layer));if(typeof font==="object"){element.css({font:textStyle,color:font.color})}else if(typeof font==="string"){element.addClass(font)}info=styleCache[text]={width:element.outerWidth(true),height:element.outerHeight(true),element:element,positions:[]};element.detach()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions;if(halign=="center"){x-=info.width/2}else if(halign=="right"){x-=info.width}if(valign=="middle"){y-=info.height/2}else if(valign=="bottom"){y-=info.height}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,rendered:false,element:positions.length?info.element.clone():info.element,x:x,y:y};positions.push(position);position.element.css({top:Math.round(y),left:Math.round(x),"text-align":halign})};Canvas.prototype.removeText=function(layer,x,y,text,font,angle){if(text==null){var layerCache=this._textCache[layer];if(layerCache!=null){for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){position.active=false}}}}}}}else{var positions=this.getTextInfo(layer,text,font,angle).positions;for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=false}}}};function Plot(placeholder,data_,options_,plugins){var series=[],options={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false,zero:true},shadowSize:3,highlightColor:null},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1e3/60},hooks:{}},surface=null,overlay=null,eventHolder=null,ctx=null,octx=null,xaxes=[],yaxes=[],plotOffset={left:0,right:0,top:0,bottom:0},plotWidth=0,plotHeight=0,hooks={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},plot=this;plot.setData=setData;plot.setupGrid=setupGrid;plot.draw=draw;plot.getPlaceholder=function(){return placeholder};plot.getCanvas=function(){return surface.element};plot.getPlotOffset=function(){return plotOffset};plot.width=function(){return plotWidth};plot.height=function(){return plotHeight};plot.offset=function(){var o=eventHolder.offset();o.left+=plotOffset.left;o.top+=plotOffset.top;return o};plot.getData=function(){return series};plot.getAxes=function(){var res={},i;$.each(xaxes.concat(yaxes),function(_,axis){if(axis)res[axis.direction+(axis.n!=1?axis.n:"")+"axis"]=axis});return res};plot.getXAxes=function(){return xaxes};plot.getYAxes=function(){return yaxes};plot.c2p=canvasToAxisCoords;plot.p2c=axisToCanvasCoords;plot.getOptions=function(){return options};plot.highlight=highlight;plot.unhighlight=unhighlight;plot.triggerRedrawOverlay=triggerRedrawOverlay;plot.pointOffset=function(point){return{left:parseInt(xaxes[axisNumber(point,"x")-1].p2c(+point.x)+plotOffset.left,10),top:parseInt(yaxes[axisNumber(point,"y")-1].p2c(+point.y)+plotOffset.top,10)}};plot.shutdown=shutdown;plot.destroy=function(){shutdown();placeholder.removeData("plot").empty();series=[];options=null;surface=null;overlay=null;eventHolder=null;ctx=null;octx=null;xaxes=[];yaxes=[];hooks=null;highlights=[];plot=null};plot.resize=function(){var width=placeholder.width(),height=placeholder.height();surface.resize(width,height);overlay.resize(width,height)};plot.hooks=hooks;initPlugins(plot);parseOptions(options_);setupCanvases();setData(data_);setupGrid();draw();bindEvents();function executeHooks(hook,args){args=[plot].concat(args);for(var i=0;imaxIndex){maxIndex=sc}}}if(neededColors<=maxIndex){neededColors=maxIndex+1}var c,colors=[],colorPool=options.colors,colorPoolSize=colorPool.length,variation=0;for(i=0;i=0){if(variation<.5){variation=-variation-.2}else variation=0}else variation=-variation}colors[i]=c.scale("rgb",1+variation)}var colori=0,s;for(i=0;iaxis.datamax&&max!=fakeInfinity)axis.datamax=max}$.each(allAxes(),function(_,axis){axis.datamin=topSentry;axis.datamax=bottomSentry;axis.used=false});for(i=0;i0&&points[k-ps]!=null&&points[k-ps]!=points[k]&&points[k-ps+1]!=points[k+1]){for(m=0;mxmax)xmax=val}if(f.y){if(valymax)ymax=val}}}if(s.bars.show){var delta;switch(s.bars.align){case"left":delta=0;break;case"right":delta=-s.bars.barWidth;break;default:delta=-s.bars.barWidth/2}if(s.bars.horizontal){ymin+=delta;ymax+=delta+s.bars.barWidth}else{xmin+=delta;xmax+=delta+s.bars.barWidth}}updateAxis(s.xaxis,xmin,xmax);updateAxis(s.yaxis,ymin,ymax)}$.each(allAxes(),function(_,axis){if(axis.datamin==topSentry)axis.datamin=null;if(axis.datamax==bottomSentry)axis.datamax=null})}function setupCanvases(){placeholder.css("padding",0).children().filter(function(){return!$(this).hasClass("flot-overlay")&&!$(this).hasClass("flot-base")}).remove();if(placeholder.css("position")=="static")placeholder.css("position","relative");surface=new Canvas("flot-base",placeholder);overlay=new Canvas("flot-overlay",placeholder);ctx=surface.context;octx=overlay.context;eventHolder=$(overlay.element).unbind();var existing=placeholder.data("plot");if(existing){existing.shutdown();overlay.clear()}placeholder.data("plot",plot)}function bindEvents(){if(options.grid.hoverable){eventHolder.mousemove(onMouseMove);eventHolder.bind("mouseleave",onMouseLeave)}if(options.grid.clickable)eventHolder.click(onClick);executeHooks(hooks.bindEvents,[eventHolder])}function shutdown(){if(redrawTimeout)clearTimeout(redrawTimeout);eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mouseleave",onMouseLeave);eventHolder.unbind("click",onClick);executeHooks(hooks.shutdown,[eventHolder])}function setTransformationHelpers(axis){function identity(x){return x}var s,m,t=axis.options.transform||identity,it=axis.options.inverseTransform;if(axis.direction=="x"){s=axis.scale=plotWidth/Math.abs(t(axis.max)-t(axis.min));m=Math.min(t(axis.max),t(axis.min))}else{s=axis.scale=plotHeight/Math.abs(t(axis.max)-t(axis.min));s=-s;m=Math.max(t(axis.max),t(axis.min))}if(t==identity)axis.p2c=function(p){return(p-m)*s};else axis.p2c=function(p){return(t(p)-m)*s};if(!it)axis.c2p=function(c){return m+c/s};else axis.c2p=function(c){return it(m+c/s)}}function measureTickLabels(axis){var opts=axis.options,ticks=axis.ticks||[],labelWidth=opts.labelWidth||0,labelHeight=opts.labelHeight||0,maxWidth=labelWidth||(axis.direction=="x"?Math.floor(surface.width/(ticks.length||1)):null),legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=opts.font||"flot-tick-label tickLabel";for(var i=0;i=0;--i)allocateAxisBoxFirstPhase(allocatedAxes[i]);adjustLayoutForThingsStickingOut();$.each(allocatedAxes,function(_,axis){allocateAxisBoxSecondPhase(axis)})}plotWidth=surface.width-plotOffset.left-plotOffset.right;plotHeight=surface.height-plotOffset.bottom-plotOffset.top;$.each(axes,function(_,axis){setTransformationHelpers(axis)});if(showGrid){drawAxisLabels()}insertLegend()}function setRange(axis){var opts=axis.options,min=+(opts.min!=null?opts.min:axis.datamin),max=+(opts.max!=null?opts.max:axis.datamax),delta=max-min;if(delta==0){var widen=max==0?1:.01;if(opts.min==null)min-=widen;if(opts.max==null||opts.min!=null)max+=widen}else{var margin=opts.autoscaleMargin;if(margin!=null){if(opts.min==null){min-=delta*margin;if(min<0&&axis.datamin!=null&&axis.datamin>=0)min=0}if(opts.max==null){max+=delta*margin;if(max>0&&axis.datamax!=null&&axis.datamax<=0)max=0}}}axis.min=min;axis.max=max}function setupTickGeneration(axis){var opts=axis.options;var noTicks;if(typeof opts.ticks=="number"&&opts.ticks>0)noTicks=opts.ticks;else noTicks=.3*Math.sqrt(axis.direction=="x"?surface.width:surface.height);var delta=(axis.max-axis.min)/noTicks,dec=-Math.floor(Math.log(delta)/Math.LN10),maxDec=opts.tickDecimals;if(maxDec!=null&&dec>maxDec){dec=maxDec}var magn=Math.pow(10,-dec),norm=delta/magn,size;if(norm<1.5){size=1}else if(norm<3){size=2;if(norm>2.25&&(maxDec==null||dec+1<=maxDec)){size=2.5;++dec}}else if(norm<7.5){size=5}else{size=10}size*=magn;if(opts.minTickSize!=null&&size0){if(opts.min==null)axis.min=Math.min(axis.min,niceTicks[0]);if(opts.max==null&&niceTicks.length>1)axis.max=Math.max(axis.max,niceTicks[niceTicks.length-1])}axis.tickGenerator=function(axis){var ticks=[],v,i;for(i=0;i1&&/\..*0$/.test((ts[1]-ts[0]).toFixed(extraDec))))axis.tickDecimals=extraDec}}}}function setTicks(axis){var oticks=axis.options.ticks,ticks=[];if(oticks==null||typeof oticks=="number"&&oticks>0)ticks=axis.tickGenerator(axis);else if(oticks){if($.isFunction(oticks))ticks=oticks(axis);else ticks=oticks}var i,v;axis.ticks=[];for(i=0;i1)label=t[1]}else v=+t;if(label==null)label=axis.tickFormatter(v,axis);if(!isNaN(v))axis.ticks.push({v:v,label:label})}}function snapRangeToTicks(axis,ticks){if(axis.options.autoscaleMargin&&ticks.length>0){if(axis.options.min==null)axis.min=Math.min(axis.min,ticks[0].v);if(axis.options.max==null&&ticks.length>1)axis.max=Math.max(axis.max,ticks[ticks.length-1].v)}}function draw(){surface.clear();executeHooks(hooks.drawBackground,[ctx]);var grid=options.grid;if(grid.show&&grid.backgroundColor)drawBackground();if(grid.show&&!grid.aboveData){drawGrid()}for(var i=0;ito){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function drawBackground(){ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.fillStyle=getColorOrGradient(options.grid.backgroundColor,plotHeight,0,"rgba(255, 255, 255, 0)");ctx.fillRect(0,0,plotWidth,plotHeight);ctx.restore()}function drawGrid(){var i,axes,bw,bc;ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var markings=options.grid.markings;if(markings){if($.isFunction(markings)){axes=plot.getAxes();axes.xmin=axes.xaxis.min;axes.xmax=axes.xaxis.max;axes.ymin=axes.yaxis.min;axes.ymax=axes.yaxis.max;markings=markings(axes)}for(i=0;ixrange.axis.max||yrange.toyrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);var xequal=xrange.from===xrange.to,yequal=yrange.from===yrange.to;if(xequal&&yequal){continue}xrange.from=Math.floor(xrange.axis.p2c(xrange.from));xrange.to=Math.floor(xrange.axis.p2c(xrange.to));yrange.from=Math.floor(yrange.axis.p2c(yrange.from));yrange.to=Math.floor(yrange.axis.p2c(yrange.to));if(xequal||yequal){var lineWidth=m.lineWidth||options.grid.markingsLineWidth,subPixel=lineWidth%2?.5:0;ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=lineWidth;if(xequal){ctx.moveTo(xrange.to+subPixel,yrange.from);ctx.lineTo(xrange.to+subPixel,yrange.to)}else{ctx.moveTo(xrange.from,yrange.to+subPixel);ctx.lineTo(xrange.to,yrange.to+subPixel)}ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;jaxis.max||t=="full"&&(typeof bw=="object"&&bw[axis.position]>0||bw>0)&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0)return;for(var i=0;iaxis.max)continue;if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i=y2&&y1>axisy.max){if(y2>axisy.max)continue;x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max){if(y1>axisy.max)continue;x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1<=x2&&x1=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(x1!=prevx||y1!=prevy)ctx.moveTo(axisx.p2c(x1)+xoffset,axisy.p2c(y1)+yoffset);prevx=x2;prevy=y2;ctx.lineTo(axisx.p2c(x2)+xoffset,axisy.p2c(y2)+yoffset)}ctx.stroke()}function plotLineArea(datapoints,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,bottom=Math.min(Math.max(0,axisy.min),axisy.max),i=0,top,areaOpen=false,ypos=1,segmentStart=0,segmentEnd=0;while(true){if(ps>0&&i>points.length+ps)break;i+=ps;var x1=points[i-ps],y1=points[i-ps+ypos],x2=points[i],y2=points[i+ypos];if(areaOpen){if(ps>0&&x1!=null&&x2==null){segmentEnd=i;ps=-ps;ypos=2;continue}if(ps<0&&i==segmentStart+ps){ctx.fill();areaOpen=false;ps=-ps;ypos=1;i=segmentStart=segmentEnd+ps;continue}}if(x1==null||x2==null)continue;if(x1<=x2&&x1=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(!areaOpen){ctx.beginPath();ctx.moveTo(axisx.p2c(x1),axisy.p2c(bottom));areaOpen=true}if(y1>=axisy.max&&y2>=axisy.max){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.max));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.max));continue}else if(y1<=axisy.min&&y2<=axisy.min){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.min));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.min));continue}var x1old=x1,x2old=x2;if(y1<=y2&&y1=axisy.min){x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2=axisy.min){x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max&&y2<=axisy.max){x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max&&y1<=axisy.max){x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1!=x1old){ctx.lineTo(axisx.p2c(x1old),axisy.p2c(y1))}ctx.lineTo(axisx.p2c(x1),axisy.p2c(y1));ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));if(x2!=x2old){ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));ctx.lineTo(axisx.p2c(x2old),axisy.p2c(y2))}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var lw=series.lines.lineWidth,sw=series.shadowSize;if(lw>0&&sw>0){ctx.lineWidth=sw;ctx.strokeStyle="rgba(0,0,0,0.1)";var angle=Math.PI/18;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/2),Math.cos(angle)*(lw/2+sw/2),series.xaxis,series.yaxis);ctx.lineWidth=sw/2;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/4),Math.cos(angle)*(lw/2+sw/4),series.xaxis,series.yaxis)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;var fillStyle=getFillStyle(series.lines,series.color,0,plotHeight);if(fillStyle){ctx.fillStyle=fillStyle;plotLineArea(series.datapoints,series.xaxis,series.yaxis)}if(lw>0)plotLine(series.datapoints,0,0,series.xaxis,series.yaxis);ctx.restore()}function drawSeriesPoints(series){function plotPoints(datapoints,radius,fillStyle,offset,shadow,axisx,axisy,symbol){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;iaxisx.max||yaxisy.max)continue;ctx.beginPath();x=axisx.p2c(x);y=axisy.p2c(y)+offset;if(symbol=="circle")ctx.arc(x,y,radius,0,shadow?Math.PI:Math.PI*2,false);else symbol(ctx,x,y,radius,shadow);ctx.closePath();if(fillStyle){ctx.fillStyle=fillStyle;ctx.fill()}ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var lw=series.points.lineWidth,sw=series.shadowSize,radius=series.points.radius,symbol=series.points.symbol;if(lw==0)lw=1e-4;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPoints(series.datapoints,radius,null,w+w/2,true,series.xaxis,series.yaxis,symbol);ctx.strokeStyle="rgba(0,0,0,0.2)";plotPoints(series.datapoints,radius,null,w/2,true,series.xaxis,series.yaxis,symbol)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;plotPoints(series.datapoints,radius,getFillStyle(series.points,series.color),0,false,series.xaxis,series.yaxis,symbol);ctx.restore()}function drawBar(x,y,b,barLeft,barRight,fillStyleCallback,axisx,axisy,c,horizontal,lineWidth){var left,right,bottom,top,drawLeft,drawRight,drawTop,drawBottom,tmp;if(horizontal){drawBottom=drawRight=drawTop=true;drawLeft=false;left=b;right=x;top=y+barLeft;bottom=y+barRight;if(rightaxisx.max||topaxisy.max)return;if(leftaxisx.max){right=axisx.max;drawRight=false}if(bottomaxisy.max){top=axisy.max;drawTop=false}left=axisx.p2c(left);bottom=axisy.p2c(bottom);right=axisx.p2c(right);top=axisy.p2c(top);if(fillStyleCallback){c.fillStyle=fillStyleCallback(bottom,top);c.fillRect(left,top,right-left,bottom-top)}if(lineWidth>0&&(drawLeft||drawRight||drawTop||drawBottom)){c.beginPath();c.moveTo(left,bottom);if(drawLeft)c.lineTo(left,top);else c.moveTo(left,top);if(drawTop)c.lineTo(right,top);else c.moveTo(right,top);if(drawRight)c.lineTo(right,bottom);else c.moveTo(right,bottom);if(drawBottom)c.lineTo(left,bottom);else c.moveTo(left,bottom);c.stroke()}}function drawSeriesBars(series){function plotBars(datapoints,barLeft,barRight,fillStyleCallback,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i");fragments.push("");rowStarted=true}fragments.push('
    '+''+entry.label+"")}if(rowStarted)fragments.push("");if(fragments.length==0)return;var table=''+fragments.join("")+"
    ";if(options.legend.container!=null)$(options.legend.container).html(table);else{var pos="",p=options.legend.position,m=options.legend.margin;if(m[0]==null)m=[m,m];if(p.charAt(0)=="n")pos+="top:"+(m[1]+plotOffset.top)+"px;";else if(p.charAt(0)=="s")pos+="bottom:"+(m[1]+plotOffset.bottom)+"px;";if(p.charAt(1)=="e")pos+="right:"+(m[0]+plotOffset.right)+"px;";else if(p.charAt(1)=="w")pos+="left:"+(m[0]+plotOffset.left)+"px;";var legend=$('
    '+table.replace('style="','style="position:absolute;'+pos+";")+"
    ").appendTo(placeholder);if(options.legend.backgroundOpacity!=0){var c=options.legend.backgroundColor;if(c==null){c=options.grid.backgroundColor;if(c&&typeof c=="string")c=$.color.parse(c);else c=$.color.extract(legend,"background-color");c.a=1;c=c.toString()}var div=legend.children();$('
    ').prependTo(legend).css("opacity",options.legend.backgroundOpacity)}}}var highlights=[],redrawTimeout=null;function findNearbyItem(mouseX,mouseY,seriesFilter){var maxDistance=options.grid.mouseActiveRadius,smallestDistance=maxDistance*maxDistance+1,item=null,foundPoint=false,i,j,ps;for(i=series.length-1;i>=0;--i){if(!seriesFilter(series[i]))continue;var s=series[i],axisx=s.xaxis,axisy=s.yaxis,points=s.datapoints.points,mx=axisx.c2p(mouseX),my=axisy.c2p(mouseY),maxx=maxDistance/axisx.scale,maxy=maxDistance/axisy.scale;ps=s.datapoints.pointsize;if(axisx.options.inverseTransform)maxx=Number.MAX_VALUE;if(axisy.options.inverseTransform)maxy=Number.MAX_VALUE;if(s.lines.show||s.points.show){for(j=0;jmaxx||x-mx<-maxx||y-my>maxy||y-my<-maxy)continue;var dx=Math.abs(axisx.p2c(x)-mouseX),dy=Math.abs(axisy.p2c(y)-mouseY),dist=dx*dx+dy*dy;if(dist=Math.min(b,x)&&my>=y+barLeft&&my<=y+barRight:mx>=x+barLeft&&mx<=x+barRight&&my>=Math.min(b,y)&&my<=Math.max(b,y))item=[i,j/ps]}}}if(item){i=item[0];j=item[1];ps=series[i].datapoints.pointsize;return{datapoint:series[i].datapoints.points.slice(j*ps,(j+1)*ps),dataIndex:j,series:series[i],seriesIndex:i}}return null}function onMouseMove(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return s["hoverable"]!=false})}function onMouseLeave(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return false})}function onClick(e){triggerClickHoverEvent("plotclick",e,function(s){return s["clickable"]!=false})}function triggerClickHoverEvent(eventname,event,seriesFilter){var offset=eventHolder.offset(),canvasX=event.pageX-offset.left-plotOffset.left,canvasY=event.pageY-offset.top-plotOffset.top,pos=canvasToAxisCoords({left:canvasX,top:canvasY});pos.pageX=event.pageX;pos.pageY=event.pageY;var item=findNearbyItem(canvasX,canvasY,seriesFilter);if(item){item.pageX=parseInt(item.series.xaxis.p2c(item.datapoint[0])+offset.left+plotOffset.left,10);item.pageY=parseInt(item.series.yaxis.p2c(item.datapoint[1])+offset.top+plotOffset.top,10)}if(options.grid.autoHighlight){for(var i=0;iaxisx.max||yaxisy.max)return;var pointRadius=series.points.radius+series.points.lineWidth/2;octx.lineWidth=pointRadius;octx.strokeStyle=highlightColor;var radius=1.5*pointRadius;x=axisx.p2c(x);y=axisy.p2c(y);octx.beginPath();if(series.points.symbol=="circle")octx.arc(x,y,radius,0,2*Math.PI,false);else series.points.symbol(octx,x,y,radius,false);octx.closePath();octx.stroke()}function drawBarHighlight(series,point){var highlightColor=typeof series.highlightColor==="string"?series.highlightColor:$.color.parse(series.color).scale("a",.5).toString(),fillStyle=highlightColor,barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}octx.lineWidth=series.bars.lineWidth;octx.strokeStyle=highlightColor;drawBar(point[0],point[1],point[2]||0,barLeft,barLeft+series.bars.barWidth,function(){return fillStyle},series.xaxis,series.yaxis,octx,series.bars.horizontal,series.bars.lineWidth)}function getColorOrGradient(spec,bottom,top,defaultColor){if(typeof spec=="string")return spec;else{var gradient=ctx.createLinearGradient(0,top,0,bottom);for(var i=0,l=spec.colors.length;i=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-matte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-matte.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-matte@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-matte@2x.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-plain.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-shadow.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-shadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-shadow@2x.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-soft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-soft.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/images/markers-soft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet-awesome-markers/images/markers-soft@2x.png -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/leaflet.awesome-markers.css: -------------------------------------------------------------------------------- 1 | /* 2 | Author: L. Voogdt 3 | License: MIT 4 | Version: 1.0 5 | */ 6 | 7 | /* Marker setup */ 8 | .awesome-marker { 9 | background: url('images/markers-soft.png') no-repeat 0 0; 10 | width: 35px; 11 | height: 46px; 12 | position:absolute; 13 | left:0; 14 | top:0; 15 | display: block; 16 | text-align: center; 17 | } 18 | 19 | .awesome-marker-shadow { 20 | background: url('images/markers-shadow.png') no-repeat 0 0; 21 | width: 36px; 22 | height: 16px; 23 | } 24 | 25 | /* Retina displays */ 26 | @media (min--moz-device-pixel-ratio: 1.5),(-o-min-device-pixel-ratio: 3/2), 27 | (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5),(min-resolution: 1.5dppx) { 28 | .awesome-marker { 29 | background-image: url('images/markers-soft@2x.png'); 30 | background-size: 720px 46px; 31 | } 32 | .awesome-marker-shadow { 33 | background-image: url('images/markers-shadow@2x.png'); 34 | background-size: 35px 16px; 35 | } 36 | } 37 | 38 | .awesome-marker i { 39 | color: #333; 40 | margin-top: 10px; 41 | display: inline-block; 42 | font-size: 14px; 43 | } 44 | 45 | .awesome-marker .icon-white { 46 | color: #fff; 47 | } 48 | 49 | /* Colors */ 50 | .awesome-marker-icon-red { 51 | background-position: 0 0; 52 | } 53 | 54 | .awesome-marker-icon-darkred { 55 | background-position: -180px 0; 56 | } 57 | 58 | .awesome-marker-icon-lightred { 59 | background-position: -360px 0; 60 | } 61 | 62 | .awesome-marker-icon-orange { 63 | background-position: -36px 0; 64 | } 65 | 66 | .awesome-marker-icon-beige { 67 | background-position: -396px 0; 68 | } 69 | 70 | .awesome-marker-icon-green { 71 | background-position: -72px 0; 72 | } 73 | 74 | .awesome-marker-icon-darkgreen { 75 | background-position: -252px 0; 76 | } 77 | 78 | .awesome-marker-icon-lightgreen { 79 | background-position: -432px 0; 80 | } 81 | 82 | .awesome-marker-icon-blue { 83 | background-position: -108px 0; 84 | } 85 | 86 | .awesome-marker-icon-darkblue { 87 | background-position: -216px 0; 88 | } 89 | 90 | .awesome-marker-icon-lightblue { 91 | background-position: -468px 0; 92 | } 93 | 94 | .awesome-marker-icon-purple { 95 | background-position: -144px 0; 96 | } 97 | 98 | .awesome-marker-icon-darkpurple { 99 | background-position: -288px 0; 100 | } 101 | 102 | .awesome-marker-icon-pink { 103 | background-position: -504px 0; 104 | } 105 | 106 | .awesome-marker-icon-cadetblue { 107 | background-position: -324px 0; 108 | } 109 | 110 | .awesome-marker-icon-white { 111 | background-position: -574px 0; 112 | } 113 | 114 | .awesome-marker-icon-gray { 115 | background-position: -648px 0; 116 | } 117 | 118 | .awesome-marker-icon-lightgray { 119 | background-position: -612px 0; 120 | } 121 | 122 | .awesome-marker-icon-black { 123 | background-position: -682px 0; 124 | } 125 | -------------------------------------------------------------------------------- /lib/leaflet-awesome-markers/leaflet.awesome-markers.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons 3 | (c) 2012-2013, Lennard Voogdt 4 | 5 | http://leafletjs.com 6 | https://github.com/lvoogdt 7 | *//*global L*/(function(e,t,n){"use strict";L.AwesomeMarkers={};L.AwesomeMarkers.version="2.0.1";L.AwesomeMarkers.Icon=L.Icon.extend({options:{iconSize:[35,45],iconAnchor:[17,42],popupAnchor:[1,-32],shadowAnchor:[10,12],shadowSize:[36,16],className:"awesome-marker",prefix:"glyphicon",spinClass:"fa-spin",icon:"home",markerColor:"blue",iconColor:"white"},initialize:function(e){e=L.Util.setOptions(this,e)},createIcon:function(){var e=t.createElement("div"),n=this.options;n.icon&&(e.innerHTML=this._createInner());n.bgPos&&(e.style.backgroundPosition=-n.bgPos.x+"px "+ -n.bgPos.y+"px");this._setIconStyles(e,"icon-"+n.markerColor);return e},_createInner:function(){var e,t="",n="",r="",i=this.options;i.icon.slice(0,i.prefix.length+1)===i.prefix+"-"?e=i.icon:e=i.prefix+"-"+i.icon;i.spin&&typeof i.spinClass=="string"&&(t=i.spinClass);i.iconColor&&(i.iconColor==="white"||i.iconColor==="black"?n="icon-"+i.iconColor:r="style='color: "+i.iconColor+"' ");return""},_setIconStyles:function(e,t){var n=this.options,r=L.point(n[t==="shadow"?"shadowSize":"iconSize"]),i;t==="shadow"?i=L.point(n.shadowAnchor||n.iconAnchor):i=L.point(n.iconAnchor);!i&&r&&(i=r.divideBy(2,!0));e.className="awesome-marker-"+t+" "+n.className;if(i){e.style.marginLeft=-i.x+"px";e.style.marginTop=-i.y+"px"}if(r){e.style.width=r.x+"px";e.style.height=r.y+"px"}},createShadow:function(){var e=t.createElement("div");this._setIconStyles(e,"shadow");return e}});L.AwesomeMarkers.icon=function(e){return new L.AwesomeMarkers.Icon(e)}})(this,document); 8 | -------------------------------------------------------------------------------- /lib/leaflet/Semicircle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Semicircle extension for L.Circle. 3 | * Jan Pieter Waagmeester 4 | */ 5 | 6 | /*jshint browser:true, strict:false, globalstrict:false, indent:4, white:true, smarttabs:true*/ 7 | /*global L:true*/ 8 | 9 | (function (L) { 10 | 11 | // save original getPathString function to draw a full circle. 12 | var original_getPathString = L.Circle.prototype.getPathString; 13 | 14 | L.Circle = L.Circle.extend({ 15 | options: { 16 | startAngle: 0, 17 | stopAngle: 359.9999 18 | }, 19 | 20 | // make sure 0 degrees is up (North) and convert to radians. 21 | _fixAngle: function (angle) { 22 | return (angle - 90) * L.LatLng.DEG_TO_RAD; 23 | }, 24 | startAngle: function () { 25 | return this._fixAngle(this.options.startAngle); 26 | }, 27 | stopAngle: function () { 28 | return this._fixAngle(this.options.stopAngle); 29 | }, 30 | 31 | //rotate point x,y+r around x,y with angle. 32 | rotated: function (angle, r) { 33 | return this._point.add( 34 | L.point(Math.cos(angle), Math.sin(angle)).multiplyBy(r) 35 | ).round(); 36 | }, 37 | 38 | getPathString: function () { 39 | var center = this._point, 40 | r = this._radius; 41 | 42 | // If we want a circle, we use the original function 43 | if (this.options.startAngle === 0 && this.options.stopAngle > 359) { 44 | return original_getPathString.call(this); 45 | } 46 | 47 | var start = this.rotated(this.startAngle(), r), 48 | end = this.rotated(this.stopAngle(), r); 49 | 50 | if (this._checkIfEmpty()) { 51 | return ''; 52 | } 53 | 54 | if (L.Browser.svg) { 55 | var largeArc = (this.options.stopAngle - this.options.startAngle >= 180) ? '1' : '0'; 56 | //move to center 57 | var ret = "M" + center.x + "," + center.y; 58 | //lineTo point on circle startangle from center 59 | ret += "L " + start.x + "," + start.y; 60 | //make circle from point start - end: 61 | ret += "A " + r + "," + r + ",0," + largeArc + ",1," + end.x + "," + end.y + " z"; 62 | 63 | return ret; 64 | } else { 65 | //TODO: fix this for semicircle... 66 | center._round(); 67 | r = Math.round(r); 68 | return "A " + center.x + "," + center.y + " " + r + "," + r + " 0," + (65535 * 360); 69 | } 70 | }, 71 | setStartAngle: function (angle) { 72 | alert("hello"); 73 | this.options.startAngle = angle; 74 | return this.redraw(); 75 | }, 76 | setStopAngle: function (angle) { 77 | this.options.stopAngle = angle; 78 | return this.redraw(); 79 | }, 80 | setDirection: function (direction, degrees) { 81 | if (degrees === undefined) { 82 | degrees = 10; 83 | } 84 | this.options.startAngle = direction - (degrees / 2); 85 | this.options.stopAngle = direction + (degrees / 2); 86 | 87 | return this.redraw(); 88 | } 89 | }); 90 | L.Circle.include(!L.Path.CANVAS ? {} : { 91 | _drawPath: function () { 92 | var center = this._point, 93 | r = this._radius; 94 | 95 | var start = this.rotated(this.startAngle(), r); 96 | 97 | this._ctx.beginPath(); 98 | this._ctx.moveTo(center.x, center.y); 99 | this._ctx.lineTo(start.x, start.y); 100 | 101 | this._ctx.arc(center.x, center.y, this._radius, 102 | this.startAngle(), this.stopAngle(), false); 103 | this._ctx.lineTo(center.x, center.y); 104 | } 105 | 106 | // _containsPoint: function (p) { 107 | // TODO: fix for semicircle. 108 | // var center = this._point, 109 | // w2 = this.options.stroke ? this.options.weight / 2 : 0; 110 | 111 | // return (p.distanceTo(center) <= this._radius + w2); 112 | // } 113 | }); 114 | })(L); 115 | -------------------------------------------------------------------------------- /lib/leaflet/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet/images/layers-2x.png -------------------------------------------------------------------------------- /lib/leaflet/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet/images/layers.png -------------------------------------------------------------------------------- /lib/leaflet/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet/images/marker-icon-2x.png -------------------------------------------------------------------------------- /lib/leaflet/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet/images/marker-icon.png -------------------------------------------------------------------------------- /lib/leaflet/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alistairmgreen/jsigc/8e70c5ef16d9992b34ea8c82c34852939d33b87b/lib/leaflet/images/marker-shadow.png -------------------------------------------------------------------------------- /lib/leaflet/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-map-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-pane, 8 | .leaflet-tile-container, 9 | .leaflet-overlay-pane, 10 | .leaflet-shadow-pane, 11 | .leaflet-marker-pane, 12 | .leaflet-popup-pane, 13 | .leaflet-overlay-pane svg, 14 | .leaflet-zoom-box, 15 | .leaflet-image-layer, 16 | .leaflet-layer { 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | } 21 | .leaflet-container { 22 | overflow: hidden; 23 | -ms-touch-action: none; 24 | } 25 | .leaflet-tile, 26 | .leaflet-marker-icon, 27 | .leaflet-marker-shadow { 28 | -webkit-user-select: none; 29 | -moz-user-select: none; 30 | user-select: none; 31 | -webkit-user-drag: none; 32 | } 33 | .leaflet-marker-icon, 34 | .leaflet-marker-shadow { 35 | display: block; 36 | } 37 | /* map is broken in FF if you have max-width: 100% on tiles */ 38 | .leaflet-container img { 39 | max-width: none !important; 40 | } 41 | /* stupid Android 2 doesn't understand "max-width: none" properly */ 42 | .leaflet-container img.leaflet-image-layer { 43 | max-width: 15000px !important; 44 | } 45 | .leaflet-tile { 46 | filter: inherit; 47 | visibility: hidden; 48 | } 49 | .leaflet-tile-loaded { 50 | visibility: inherit; 51 | } 52 | .leaflet-zoom-box { 53 | width: 0; 54 | height: 0; 55 | } 56 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 57 | .leaflet-overlay-pane svg { 58 | -moz-user-select: none; 59 | } 60 | 61 | .leaflet-tile-pane { z-index: 2; } 62 | .leaflet-objects-pane { z-index: 3; } 63 | .leaflet-overlay-pane { z-index: 4; } 64 | .leaflet-shadow-pane { z-index: 5; } 65 | .leaflet-marker-pane { z-index: 6; } 66 | .leaflet-popup-pane { z-index: 7; } 67 | 68 | .leaflet-vml-shape { 69 | width: 1px; 70 | height: 1px; 71 | } 72 | .lvml { 73 | behavior: url(#default#VML); 74 | display: inline-block; 75 | position: absolute; 76 | } 77 | 78 | 79 | /* control positioning */ 80 | 81 | .leaflet-control { 82 | position: relative; 83 | z-index: 7; 84 | pointer-events: auto; 85 | } 86 | .leaflet-top, 87 | .leaflet-bottom { 88 | position: absolute; 89 | z-index: 1000; 90 | pointer-events: none; 91 | } 92 | .leaflet-top { 93 | top: 0; 94 | } 95 | .leaflet-right { 96 | right: 0; 97 | } 98 | .leaflet-bottom { 99 | bottom: 0; 100 | } 101 | .leaflet-left { 102 | left: 0; 103 | } 104 | .leaflet-control { 105 | float: left; 106 | clear: both; 107 | } 108 | .leaflet-right .leaflet-control { 109 | float: right; 110 | } 111 | .leaflet-top .leaflet-control { 112 | margin-top: 10px; 113 | } 114 | .leaflet-bottom .leaflet-control { 115 | margin-bottom: 10px; 116 | } 117 | .leaflet-left .leaflet-control { 118 | margin-left: 10px; 119 | } 120 | .leaflet-right .leaflet-control { 121 | margin-right: 10px; 122 | } 123 | 124 | 125 | /* zoom and fade animations */ 126 | 127 | .leaflet-fade-anim .leaflet-tile, 128 | .leaflet-fade-anim .leaflet-popup { 129 | opacity: 0; 130 | -webkit-transition: opacity 0.2s linear; 131 | -moz-transition: opacity 0.2s linear; 132 | -o-transition: opacity 0.2s linear; 133 | transition: opacity 0.2s linear; 134 | } 135 | .leaflet-fade-anim .leaflet-tile-loaded, 136 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 137 | opacity: 1; 138 | } 139 | 140 | .leaflet-zoom-anim .leaflet-zoom-animated { 141 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 142 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 143 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 144 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 145 | } 146 | .leaflet-zoom-anim .leaflet-tile, 147 | .leaflet-pan-anim .leaflet-tile, 148 | .leaflet-touching .leaflet-zoom-animated { 149 | -webkit-transition: none; 150 | -moz-transition: none; 151 | -o-transition: none; 152 | transition: none; 153 | } 154 | 155 | .leaflet-zoom-anim .leaflet-zoom-hide { 156 | visibility: hidden; 157 | } 158 | 159 | 160 | /* cursors */ 161 | 162 | .leaflet-clickable { 163 | cursor: pointer; 164 | } 165 | .leaflet-container { 166 | cursor: -webkit-grab; 167 | cursor: -moz-grab; 168 | } 169 | .leaflet-popup-pane, 170 | .leaflet-control { 171 | cursor: auto; 172 | } 173 | .leaflet-dragging .leaflet-container, 174 | .leaflet-dragging .leaflet-clickable { 175 | cursor: move; 176 | cursor: -webkit-grabbing; 177 | cursor: -moz-grabbing; 178 | } 179 | 180 | 181 | /* visual tweaks */ 182 | 183 | .leaflet-container { 184 | background: #ddd; 185 | outline: 0; 186 | } 187 | .leaflet-container a { 188 | color: #0078A8; 189 | } 190 | .leaflet-container a.leaflet-active { 191 | outline: 2px solid orange; 192 | } 193 | .leaflet-zoom-box { 194 | border: 2px dotted #38f; 195 | background: rgba(255,255,255,0.5); 196 | } 197 | 198 | 199 | /* general typography */ 200 | .leaflet-container { 201 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 202 | } 203 | 204 | 205 | /* general toolbar styles */ 206 | 207 | .leaflet-bar { 208 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 209 | border-radius: 4px; 210 | } 211 | .leaflet-bar a, 212 | .leaflet-bar a:hover { 213 | background-color: #fff; 214 | border-bottom: 1px solid #ccc; 215 | width: 26px; 216 | height: 26px; 217 | line-height: 26px; 218 | display: block; 219 | text-align: center; 220 | text-decoration: none; 221 | color: black; 222 | } 223 | .leaflet-bar a, 224 | .leaflet-control-layers-toggle { 225 | background-position: 50% 50%; 226 | background-repeat: no-repeat; 227 | display: block; 228 | } 229 | .leaflet-bar a:hover { 230 | background-color: #f4f4f4; 231 | } 232 | .leaflet-bar a:first-child { 233 | border-top-left-radius: 4px; 234 | border-top-right-radius: 4px; 235 | } 236 | .leaflet-bar a:last-child { 237 | border-bottom-left-radius: 4px; 238 | border-bottom-right-radius: 4px; 239 | border-bottom: none; 240 | } 241 | .leaflet-bar a.leaflet-disabled { 242 | cursor: default; 243 | background-color: #f4f4f4; 244 | color: #bbb; 245 | } 246 | 247 | .leaflet-touch .leaflet-bar a { 248 | width: 30px; 249 | height: 30px; 250 | line-height: 30px; 251 | } 252 | 253 | 254 | /* zoom control */ 255 | 256 | .leaflet-control-zoom-in, 257 | .leaflet-control-zoom-out { 258 | font: bold 18px 'Lucida Console', Monaco, monospace; 259 | text-indent: 1px; 260 | } 261 | .leaflet-control-zoom-out { 262 | font-size: 20px; 263 | } 264 | 265 | .leaflet-touch .leaflet-control-zoom-in { 266 | font-size: 22px; 267 | } 268 | .leaflet-touch .leaflet-control-zoom-out { 269 | font-size: 24px; 270 | } 271 | 272 | 273 | /* layers control */ 274 | 275 | .leaflet-control-layers { 276 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 277 | background: #fff; 278 | border-radius: 5px; 279 | } 280 | .leaflet-control-layers-toggle { 281 | background-image: url(images/layers.png); 282 | width: 36px; 283 | height: 36px; 284 | } 285 | .leaflet-retina .leaflet-control-layers-toggle { 286 | background-image: url(images/layers-2x.png); 287 | background-size: 26px 26px; 288 | } 289 | .leaflet-touch .leaflet-control-layers-toggle { 290 | width: 44px; 291 | height: 44px; 292 | } 293 | .leaflet-control-layers .leaflet-control-layers-list, 294 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 295 | display: none; 296 | } 297 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 298 | display: block; 299 | position: relative; 300 | } 301 | .leaflet-control-layers-expanded { 302 | padding: 6px 10px 6px 6px; 303 | color: #333; 304 | background: #fff; 305 | } 306 | .leaflet-control-layers-selector { 307 | margin-top: 2px; 308 | position: relative; 309 | top: 1px; 310 | } 311 | .leaflet-control-layers label { 312 | display: block; 313 | } 314 | .leaflet-control-layers-separator { 315 | height: 0; 316 | border-top: 1px solid #ddd; 317 | margin: 5px -10px 5px -6px; 318 | } 319 | 320 | 321 | /* attribution and scale controls */ 322 | 323 | .leaflet-container .leaflet-control-attribution { 324 | background: #fff; 325 | background: rgba(255, 255, 255, 0.7); 326 | margin: 0; 327 | } 328 | .leaflet-control-attribution, 329 | .leaflet-control-scale-line { 330 | padding: 0 5px; 331 | color: #333; 332 | } 333 | .leaflet-control-attribution a { 334 | text-decoration: none; 335 | } 336 | .leaflet-control-attribution a:hover { 337 | text-decoration: underline; 338 | } 339 | .leaflet-container .leaflet-control-attribution, 340 | .leaflet-container .leaflet-control-scale { 341 | font-size: 11px; 342 | } 343 | .leaflet-left .leaflet-control-scale { 344 | margin-left: 5px; 345 | } 346 | .leaflet-bottom .leaflet-control-scale { 347 | margin-bottom: 5px; 348 | } 349 | .leaflet-control-scale-line { 350 | border: 2px solid #777; 351 | border-top: none; 352 | line-height: 1.1; 353 | padding: 2px 5px 1px; 354 | font-size: 11px; 355 | white-space: nowrap; 356 | overflow: hidden; 357 | -moz-box-sizing: content-box; 358 | box-sizing: content-box; 359 | 360 | background: #fff; 361 | background: rgba(255, 255, 255, 0.5); 362 | } 363 | .leaflet-control-scale-line:not(:first-child) { 364 | border-top: 2px solid #777; 365 | border-bottom: none; 366 | margin-top: -2px; 367 | } 368 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 369 | border-bottom: 2px solid #777; 370 | } 371 | 372 | .leaflet-touch .leaflet-control-attribution, 373 | .leaflet-touch .leaflet-control-layers, 374 | .leaflet-touch .leaflet-bar { 375 | box-shadow: none; 376 | } 377 | .leaflet-touch .leaflet-control-layers, 378 | .leaflet-touch .leaflet-bar { 379 | border: 2px solid rgba(0,0,0,0.2); 380 | background-clip: padding-box; 381 | } 382 | 383 | 384 | /* popup */ 385 | 386 | .leaflet-popup { 387 | position: absolute; 388 | text-align: center; 389 | } 390 | .leaflet-popup-content-wrapper { 391 | padding: 1px; 392 | text-align: left; 393 | border-radius: 12px; 394 | } 395 | .leaflet-popup-content { 396 | margin: 13px 19px; 397 | line-height: 1.4; 398 | } 399 | .leaflet-popup-content p { 400 | margin: 18px 0; 401 | } 402 | .leaflet-popup-tip-container { 403 | margin: 0 auto; 404 | width: 40px; 405 | height: 20px; 406 | position: relative; 407 | overflow: hidden; 408 | } 409 | .leaflet-popup-tip { 410 | width: 17px; 411 | height: 17px; 412 | padding: 1px; 413 | 414 | margin: -10px auto 0; 415 | 416 | -webkit-transform: rotate(45deg); 417 | -moz-transform: rotate(45deg); 418 | -ms-transform: rotate(45deg); 419 | -o-transform: rotate(45deg); 420 | transform: rotate(45deg); 421 | } 422 | .leaflet-popup-content-wrapper, 423 | .leaflet-popup-tip { 424 | background: white; 425 | 426 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 427 | } 428 | .leaflet-container a.leaflet-popup-close-button { 429 | position: absolute; 430 | top: 0; 431 | right: 0; 432 | padding: 4px 4px 0 0; 433 | text-align: center; 434 | width: 18px; 435 | height: 14px; 436 | font: 16px/14px Tahoma, Verdana, sans-serif; 437 | color: #c3c3c3; 438 | text-decoration: none; 439 | font-weight: bold; 440 | background: transparent; 441 | } 442 | .leaflet-container a.leaflet-popup-close-button:hover { 443 | color: #999; 444 | } 445 | .leaflet-popup-scrolled { 446 | overflow: auto; 447 | border-bottom: 1px solid #ddd; 448 | border-top: 1px solid #ddd; 449 | } 450 | 451 | .leaflet-oldie .leaflet-popup-content-wrapper { 452 | zoom: 1; 453 | } 454 | .leaflet-oldie .leaflet-popup-tip { 455 | width: 24px; 456 | margin: 0 auto; 457 | 458 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 459 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 460 | } 461 | .leaflet-oldie .leaflet-popup-tip-container { 462 | margin-top: -1px; 463 | } 464 | 465 | .leaflet-oldie .leaflet-control-zoom, 466 | .leaflet-oldie .leaflet-control-layers, 467 | .leaflet-oldie .leaflet-popup-content-wrapper, 468 | .leaflet-oldie .leaflet-popup-tip { 469 | border: 1px solid #999; 470 | } 471 | 472 | 473 | /* div icon */ 474 | 475 | .leaflet-div-icon { 476 | background: #fff; 477 | border: 1px solid #666; 478 | } 479 | -------------------------------------------------------------------------------- /lib/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.10.6 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | !function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Hc.apply(null,arguments)}function b(a){Hc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Jc)d=Jc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Kc===!1&&(Kc=!0,a.updateOffset(this),Kc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){return 0>a?Math.ceil(a):Math.floor(a)}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=p(b)),c}function r(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function s(){}function t(a){return a?a.toLowerCase().replace("_","-"):a}function u(a){for(var b,c,d,e,f=0;f0;){if(d=v(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&r(e,c,!0)>=b-1)break;b--}f++}return null}function v(a){var b=null;if(!Lc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ic._abbr,require("./locale/"+a),w(b)}catch(c){}return Lc[a]}function w(a,b){var c;return a&&(c="undefined"==typeof b?y(a):x(a,b),c&&(Ic=c)),Ic._abbr}function x(a,b){return null!==b?(b.abbr=a,Lc[a]=Lc[a]||new s,Lc[a].set(b),w(a),Lc[a]):(delete Lc[a],null)}function y(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ic;if(!c(a)){if(b=v(a))return b;a=[a]}return u(a)}function z(a,b){var c=a.toLowerCase();Mc[c]=Mc[c+"s"]=Mc[b]=a}function A(a){return"string"==typeof a?Mc[a]||Mc[a.toLowerCase()]:void 0}function B(a){var b,c,d={};for(c in a)f(a,c)&&(b=A(c),b&&(d[b]=a[c]));return d}function C(b,c){return function(d){return null!=d?(E(this,b,d),a.updateOffset(this,c),this):D(this,b)}}function D(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function E(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function F(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=A(a),"function"==typeof this[a])return this[a](b);return this}function G(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function H(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Qc[a]=e),b&&(Qc[b[0]]=function(){return G(e.apply(this,arguments),b[1],b[2])}),c&&(Qc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function I(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function J(a){var b,c,d=a.match(Nc);for(b=0,c=d.length;c>b;b++)Qc[d[b]]?d[b]=Qc[d[b]]:d[b]=I(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function K(a,b){return a.isValid()?(b=L(b,a.localeData()),Pc[b]=Pc[b]||J(b),Pc[b](a)):a.localeData().invalidDate()}function L(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Oc.lastIndex=0;d>=0&&Oc.test(a);)a=a.replace(Oc,c),Oc.lastIndex=0,d-=1;return a}function M(a){return"function"==typeof a&&"[object Function]"===Object.prototype.toString.call(a)}function N(a,b,c){dd[a]=M(b)?b:function(a){return a&&c?c:b}}function O(a,b){return f(dd,a)?dd[a](b._strict,b._locale):new RegExp(P(a))}function P(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=q(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function X(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),T(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function Y(b){return null!=b?(X(this,b),a.updateOffset(this,!0),this):D(this,"Month")}function Z(){return T(this.year(),this.month())}function $(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[gd]<0||c[gd]>11?gd:c[hd]<1||c[hd]>T(c[fd],c[gd])?hd:c[id]<0||c[id]>24||24===c[id]&&(0!==c[jd]||0!==c[kd]||0!==c[ld])?id:c[jd]<0||c[jd]>59?jd:c[kd]<0||c[kd]>59?kd:c[ld]<0||c[ld]>999?ld:-1,j(a)._overflowDayOfYear&&(fd>b||b>hd)&&(b=hd),j(a).overflow=b),a}function _(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function aa(a,b){var c=!0;return g(function(){return c&&(_(a+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ba(a,b){od[a]||(_(b),od[a]=!0)}function ca(a){var b,c,d=a._i,e=pd.exec(d);if(e){for(j(a).iso=!0,b=0,c=qd.length;c>b;b++)if(qd[b][1].exec(d)){a._f=qd[b][0];break}for(b=0,c=rd.length;c>b;b++)if(rd[b][1].exec(d)){a._f+=(e[6]||" ")+rd[b][0];break}d.match(ad)&&(a._f+="Z"),va(a)}else a._isValid=!1}function da(b){var c=sd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ca(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ea(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fa(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ga(a){return ha(a)?366:365}function ha(a){return a%4===0&&a%100!==0||a%400===0}function ia(){return ha(this.year())}function ja(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Da(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ka(a){return ja(a,this._week.dow,this._week.doy).week}function la(){return this._week.dow}function ma(){return this._week.doy}function na(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function oa(a){var b=ja(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function pa(a,b,c,d,e){var f,g=6+e-d,h=fa(a,0,1+g),i=h.getUTCDay();return e>i&&(i+=7),c=null!=c?1*c:e,f=1+g+7*(b-1)-i+c,{year:f>0?a:a-1,dayOfYear:f>0?f:ga(a-1)+f}}function qa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function ra(a,b,c){return null!=a?a:null!=b?b:c}function sa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ta(a){var b,c,d,e,f=[];if(!a._d){for(d=sa(a),a._w&&null==a._a[hd]&&null==a._a[gd]&&ua(a),a._dayOfYear&&(e=ra(a._a[fd],d[fd]),a._dayOfYear>ga(e)&&(j(a)._overflowDayOfYear=!0),c=fa(e,0,a._dayOfYear),a._a[gd]=c.getUTCMonth(),a._a[hd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[id]&&0===a._a[jd]&&0===a._a[kd]&&0===a._a[ld]&&(a._nextDay=!0,a._a[id]=0),a._d=(a._useUTC?fa:ea).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[id]=24)}}function ua(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ra(b.GG,a._a[fd],ja(Da(),1,4).year),d=ra(b.W,1),e=ra(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ra(b.gg,a._a[fd],ja(Da(),f,g).year),d=ra(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=pa(c,d,e,g,f),a._a[fd]=h.year,a._dayOfYear=h.dayOfYear}function va(b){if(b._f===a.ISO_8601)return void ca(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=L(b._f,b._locale).match(Nc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Qc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),S(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[id]<=12&&b._a[id]>0&&(j(b).bigHour=void 0),b._a[id]=wa(b._locale,b._a[id],b._meridiem),ta(b),$(b)}function wa(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function xa(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function ya(a){if(!a._d){var b=B(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ta(a)}}function za(a){var b=new n($(Aa(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Aa(a){var b=a._i,e=a._f;return a._locale=a._locale||y(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),o(b)?new n($(b)):(c(e)?xa(a):e?va(a):d(b)?a._d=b:Ba(a),a))}function Ba(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?da(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ta(b)):"object"==typeof f?ya(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ca(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,za(f)}function Da(a,b,c,d){return Ca(a,b,c,d,!1)}function Ea(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Da();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+G(~~(a/60),2)+b+G(~~a%60,2)})}function Ka(a){var b=(a||"").match(ad)||[],c=b[b.length-1]||[],d=(c+"").match(xd)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?e:-e}function La(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Da(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Da(b).local()}function Ma(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Na(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ka(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ma(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?bb(this,Ya(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ma(this)}function Oa(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Pa(a){return this.utcOffset(0,a)}function Qa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ma(this),"m")),this}function Ra(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ka(this._i)),this}function Sa(a){return a=a?Da(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Ta(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ua(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var a={};if(m(a,this),a=Aa(a),a._a){var b=a._isUTC?h(a._a):Da(a._a);this._isDSTShifted=this.isValid()&&r(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Va(){return!this._isUTC}function Wa(){return this._isUTC}function Xa(){return this._isUTC&&0===this._offset}function Ya(a,b){var c,d,e,g=a,h=null;return Ia(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=yd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[hd])*c,h:q(h[id])*c,m:q(h[jd])*c,s:q(h[kd])*c,ms:q(h[ld])*c}):(h=zd.exec(a))?(c="-"===h[1]?-1:1,g={y:Za(h[2],c),M:Za(h[3],c),d:Za(h[4],c),h:Za(h[5],c),m:Za(h[6],c),s:Za(h[7],c),w:Za(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=_a(Da(g.from),Da(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ha(g),Ia(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Za(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function $a(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function _a(a,b){var c;return b=La(b,a),a.isBefore(b)?c=$a(a,b):(c=$a(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function ab(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ba(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ya(c,d),bb(this,e,a),this}}function bb(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&E(b,"Date",D(b,"Date")+g*d),h&&X(b,D(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function cb(a,b){var c=a||Da(),d=La(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(b&&b[f]||this.localeData().calendar(f,this,Da(c)))}function db(){return new n(this)}function eb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+this>+a):(c=o(a)?+a:+Da(a),c<+this.clone().startOf(b))}function fb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+a>+this):(c=o(a)?+a:+Da(a),+this.clone().endOf(b)b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function kb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function lb(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=Da([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Pb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Kb(a,this.localeData()),this.add(a-b,"d")):b}function Qb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Rb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Sb(a,b){H(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Tb(a,b){return b._meridiemParse}function Ub(a){return"p"===(a+"").toLowerCase().charAt(0)}function Vb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Wb(a,b){b[ld]=q(1e3*("0."+a))}function Xb(){return this._isUTC?"UTC":""}function Yb(){return this._isUTC?"Coordinated Universal Time":""}function Zb(a){return Da(1e3*a)}function $b(){return Da.apply(null,arguments).parseZone()}function _b(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function bc(){return this._invalidDate}function cc(a){return this._ordinal.replace("%d",a)}function dc(a){return a}function ec(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function gc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hc(a,b,c,d){var e=y(),f=h().set(d,b);return e[c](f,a)}function ic(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return hc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=hc(a,f,c,e);return g}function jc(a,b){return ic(a,b,"months",12,"month")}function kc(a,b){return ic(a,b,"monthsShort",12,"month")}function lc(a,b){return ic(a,b,"weekdays",7,"day")}function mc(a,b){return ic(a,b,"weekdaysShort",7,"day")}function nc(a,b){return ic(a,b,"weekdaysMin",7,"day")}function oc(){var a=this._data;return this._milliseconds=Wd(this._milliseconds),this._days=Wd(this._days),this._months=Wd(this._months),a.milliseconds=Wd(a.milliseconds),a.seconds=Wd(a.seconds),a.minutes=Wd(a.minutes),a.hours=Wd(a.hours),a.months=Wd(a.months),a.years=Wd(a.years),this}function pc(a,b,c,d){var e=Ya(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function qc(a,b){return pc(this,a,b,1)}function rc(a,b){return pc(this,a,b,-1)}function sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*sc(vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=p(f/1e3),i.seconds=a%60,b=p(a/60),i.minutes=b%60,c=p(b/60),i.hours=c%24,g+=p(c/24),e=p(uc(g)),h+=e,g-=sc(vc(e)),d=p(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function uc(a){return 4800*a/146097}function vc(a){return 146097*a/4800}function wc(a){var b,c,d=this._milliseconds;if(a=A(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*q(this._months/12)}function yc(a){return function(){return this.as(a)}}function zc(a){return a=A(a),this[a+"s"]()}function Ac(a){return function(){return this._data[a]}}function Bc(){return p(this.days()/7)}function Cc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Dc(a,b,c){var d=Ya(a).abs(),e=ke(d.as("s")),f=ke(d.as("m")),g=ke(d.as("h")),h=ke(d.as("d")),i=ke(d.as("M")),j=ke(d.as("y")),k=e0,k[4]=c,Cc.apply(null,k)}function Ec(a,b){return void 0===le[a]?!1:void 0===b?le[a]:(le[a]=b,!0)}function Fc(a){var b=this.localeData(),c=Dc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Gc(){var a,b,c,d=me(this._milliseconds)/1e3,e=me(this._days),f=me(this._months);a=p(d/60),b=p(a/60),d%=60,a%=60,c=p(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Hc,Ic,Jc=a.momentProperties=[],Kc=!1,Lc={},Mc={},Nc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Oc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Pc={},Qc={},Rc=/\d/,Sc=/\d\d/,Tc=/\d{3}/,Uc=/\d{4}/,Vc=/[+-]?\d{6}/,Wc=/\d\d?/,Xc=/\d{1,3}/,Yc=/\d{1,4}/,Zc=/[+-]?\d{1,6}/,$c=/\d+/,_c=/[+-]?\d+/,ad=/Z|[+-]\d\d:?\d\d/gi,bd=/[+-]?\d+(\.\d{1,3})?/,cd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,dd={},ed={},fd=0,gd=1,hd=2,id=3,jd=4,kd=5,ld=6;H("M",["MM",2],"Mo",function(){return this.month()+1}),H("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),H("MMMM",0,0,function(a){return this.localeData().months(this,a)}),z("month","M"),N("M",Wc),N("MM",Wc,Sc),N("MMM",cd),N("MMMM",cd),Q(["M","MM"],function(a,b){b[gd]=q(a)-1}),Q(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[gd]=e:j(c).invalidMonth=a});var md="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),nd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),od={};a.suppressDeprecationWarnings=!1;var pd=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,qd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],rd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],sd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=aa("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),H(0,["YY",2],0,function(){return this.year()%100}),H(0,["YYYY",4],0,"year"),H(0,["YYYYY",5],0,"year"),H(0,["YYYYYY",6,!0],0,"year"),z("year","y"),N("Y",_c),N("YY",Wc,Sc),N("YYYY",Yc,Uc),N("YYYYY",Zc,Vc),N("YYYYYY",Zc,Vc),Q(["YYYYY","YYYYYY"],fd),Q("YYYY",function(b,c){c[fd]=2===b.length?a.parseTwoDigitYear(b):q(b)}),Q("YY",function(b,c){c[fd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return q(a)+(q(a)>68?1900:2e3)};var td=C("FullYear",!1);H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),z("week","w"),z("isoWeek","W"),N("w",Wc),N("ww",Wc,Sc),N("W",Wc),N("WW",Wc,Sc),R(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=q(a)});var ud={dow:0,doy:6};H("DDD",["DDDD",3],"DDDo","dayOfYear"),z("dayOfYear","DDD"),N("DDD",Xc),N("DDDD",Tc),Q(["DDD","DDDD"],function(a,b,c){c._dayOfYear=q(a)}),a.ISO_8601=function(){};var vd=aa("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return this>a?this:a}),wd=aa("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return a>this?this:a});Ja("Z",":"),Ja("ZZ",""),N("Z",ad),N("ZZ",ad),Q(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ka(a)});var xd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var yd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,zd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ya.fn=Ha.prototype;var Ad=ab(1,"add"),Bd=ab(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Cd=aa("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Db("gggg","weekYear"),Db("ggggg","weekYear"),Db("GGGG","isoWeekYear"),Db("GGGGG","isoWeekYear"),z("weekYear","gg"),z("isoWeekYear","GG"),N("G",_c),N("g",_c),N("GG",Wc,Sc),N("gg",Wc,Sc),N("GGGG",Yc,Uc),N("gggg",Yc,Uc),N("GGGGG",Zc,Vc),N("ggggg",Zc,Vc),R(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=q(a)}),R(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),H("Q",0,0,"quarter"),z("quarter","Q"),N("Q",Rc),Q("Q",function(a,b){b[gd]=3*(q(a)-1)}),H("D",["DD",2],"Do","date"),z("date","D"),N("D",Wc),N("DD",Wc,Sc),N("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),Q(["D","DD"],hd),Q("Do",function(a,b){b[hd]=q(a.match(Wc)[0],10)});var Dd=C("Date",!0);H("d",0,"do","day"),H("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),H("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),H("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),z("day","d"),z("weekday","e"),z("isoWeekday","E"),N("d",Wc),N("e",Wc),N("E",Wc),N("dd",cd),N("ddd",cd),N("dddd",cd),R(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),R(["d","e","E"],function(a,b,c,d){b[d]=q(a)});var Ed="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Fd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Gd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");H("H",["HH",2],0,"hour"),H("h",["hh",2],0,function(){return this.hours()%12||12}),Sb("a",!0),Sb("A",!1),z("hour","h"),N("a",Tb),N("A",Tb),N("H",Wc),N("h",Wc),N("HH",Wc,Sc),N("hh",Wc,Sc),Q(["H","HH"],id),Q(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),Q(["h","hh"],function(a,b,c){b[id]=q(a),j(c).bigHour=!0});var Hd=/[ap]\.?m?\.?/i,Id=C("Hours",!0);H("m",["mm",2],0,"minute"),z("minute","m"),N("m",Wc),N("mm",Wc,Sc),Q(["m","mm"],jd);var Jd=C("Minutes",!1);H("s",["ss",2],0,"second"),z("second","s"),N("s",Wc),N("ss",Wc,Sc),Q(["s","ss"],kd);var Kd=C("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),z("millisecond","ms"),N("S",Xc,Rc),N("SS",Xc,Sc),N("SSS",Xc,Tc);var Ld;for(Ld="SSSS";Ld.length<=9;Ld+="S")N(Ld,$c);for(Ld="S";Ld.length<=9;Ld+="S")Q(Ld,Wb);var Md=C("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var Nd=n.prototype;Nd.add=Ad,Nd.calendar=cb,Nd.clone=db,Nd.diff=ib,Nd.endOf=ub,Nd.format=mb,Nd.from=nb,Nd.fromNow=ob,Nd.to=pb,Nd.toNow=qb,Nd.get=F,Nd.invalidAt=Cb,Nd.isAfter=eb,Nd.isBefore=fb,Nd.isBetween=gb,Nd.isSame=hb,Nd.isValid=Ab,Nd.lang=Cd,Nd.locale=rb,Nd.localeData=sb,Nd.max=wd,Nd.min=vd,Nd.parsingFlags=Bb,Nd.set=F,Nd.startOf=tb,Nd.subtract=Bd,Nd.toArray=yb,Nd.toObject=zb,Nd.toDate=xb,Nd.toISOString=lb,Nd.toJSON=lb,Nd.toString=kb,Nd.unix=wb,Nd.valueOf=vb,Nd.year=td,Nd.isLeapYear=ia,Nd.weekYear=Fb,Nd.isoWeekYear=Gb,Nd.quarter=Nd.quarters=Jb,Nd.month=Y,Nd.daysInMonth=Z,Nd.week=Nd.weeks=na,Nd.isoWeek=Nd.isoWeeks=oa,Nd.weeksInYear=Ib,Nd.isoWeeksInYear=Hb,Nd.date=Dd,Nd.day=Nd.days=Pb,Nd.weekday=Qb,Nd.isoWeekday=Rb,Nd.dayOfYear=qa,Nd.hour=Nd.hours=Id,Nd.minute=Nd.minutes=Jd,Nd.second=Nd.seconds=Kd, 7 | Nd.millisecond=Nd.milliseconds=Md,Nd.utcOffset=Na,Nd.utc=Pa,Nd.local=Qa,Nd.parseZone=Ra,Nd.hasAlignedHourOffset=Sa,Nd.isDST=Ta,Nd.isDSTShifted=Ua,Nd.isLocal=Va,Nd.isUtcOffset=Wa,Nd.isUtc=Xa,Nd.isUTC=Xa,Nd.zoneAbbr=Xb,Nd.zoneName=Yb,Nd.dates=aa("dates accessor is deprecated. Use date instead.",Dd),Nd.months=aa("months accessor is deprecated. Use month instead",Y),Nd.years=aa("years accessor is deprecated. Use year instead",td),Nd.zone=aa("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Oa);var Od=Nd,Pd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Qd={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Rd="Invalid date",Sd="%d",Td=/\d{1,2}/,Ud={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Vd=s.prototype;Vd._calendar=Pd,Vd.calendar=_b,Vd._longDateFormat=Qd,Vd.longDateFormat=ac,Vd._invalidDate=Rd,Vd.invalidDate=bc,Vd._ordinal=Sd,Vd.ordinal=cc,Vd._ordinalParse=Td,Vd.preparse=dc,Vd.postformat=dc,Vd._relativeTime=Ud,Vd.relativeTime=ec,Vd.pastFuture=fc,Vd.set=gc,Vd.months=U,Vd._months=md,Vd.monthsShort=V,Vd._monthsShort=nd,Vd.monthsParse=W,Vd.week=ka,Vd._week=ud,Vd.firstDayOfYear=ma,Vd.firstDayOfWeek=la,Vd.weekdays=Lb,Vd._weekdays=Ed,Vd.weekdaysMin=Nb,Vd._weekdaysMin=Gd,Vd.weekdaysShort=Mb,Vd._weekdaysShort=Fd,Vd.weekdaysParse=Ob,Vd.isPM=Ub,Vd._meridiemParse=Hd,Vd.meridiem=Vb,w("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=aa("moment.lang is deprecated. Use moment.locale instead.",w),a.langData=aa("moment.langData is deprecated. Use moment.localeData instead.",y);var Wd=Math.abs,Xd=yc("ms"),Yd=yc("s"),Zd=yc("m"),$d=yc("h"),_d=yc("d"),ae=yc("w"),be=yc("M"),ce=yc("y"),de=Ac("milliseconds"),ee=Ac("seconds"),fe=Ac("minutes"),ge=Ac("hours"),he=Ac("days"),ie=Ac("months"),je=Ac("years"),ke=Math.round,le={s:45,m:45,h:22,d:26,M:11},me=Math.abs,ne=Ha.prototype;ne.abs=oc,ne.add=qc,ne.subtract=rc,ne.as=wc,ne.asMilliseconds=Xd,ne.asSeconds=Yd,ne.asMinutes=Zd,ne.asHours=$d,ne.asDays=_d,ne.asWeeks=ae,ne.asMonths=be,ne.asYears=ce,ne.valueOf=xc,ne._bubble=tc,ne.get=zc,ne.milliseconds=de,ne.seconds=ee,ne.minutes=fe,ne.hours=ge,ne.days=he,ne.weeks=Bc,ne.months=ie,ne.years=je,ne.humanize=Fc,ne.toISOString=Gc,ne.toString=Gc,ne.toJSON=Gc,ne.locale=rb,ne.localeData=sb,ne.toIsoString=aa("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Gc),ne.lang=Cd,H("X",0,0,"unix"),H("x",0,0,"valueOf"),N("x",_c),N("X",bd),Q("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),Q("x",function(a,b,c){c._d=new Date(q(a))}),a.version="2.10.6",b(Da),a.fn=Od,a.min=Fa,a.max=Ga,a.utc=h,a.unix=Zb,a.months=jc,a.isDate=d,a.locale=w,a.invalid=l,a.duration=Ya,a.isMoment=o,a.weekdays=lc,a.parseZone=$b,a.localeData=y,a.isDuration=Ia,a.monthsShort=kc,a.weekdaysMin=nc,a.defineLocale=x,a.weekdaysShort=mc,a.normalizeUnits=A,a.relativeTimeThreshold=Ec;var oe=a;return oe}); -------------------------------------------------------------------------------- /mapcontrol.js: -------------------------------------------------------------------------------- 1 | // Wrapper for the leaflet.js map control with methods 2 | // to manage the map layers. 3 | function createMapControl(elementName) { 4 | 'use strict'; 5 | 6 | // Private methods for drawing turn point sectors and start / finish lines 7 | 8 | function getBearing(pt1, pt2) { 9 | // Get bearing from pt1 to pt2 in degrees 10 | // Formula from: http://www.movable-type.co.uk/scripts/latlong.html 11 | // Start by converting to radians. 12 | var degToRad = Math.PI / 180.0; 13 | var lat1 = pt1[0] * degToRad; 14 | var lon1 = pt1[1] * degToRad; 15 | var lat2 = pt2[0] * degToRad; 16 | var lon2 = pt2[1] * degToRad; 17 | 18 | var y = Math.sin(lon2 - lon1) * Math.cos(lat2); 19 | var x = Math.cos(lat1) * Math.sin(lat2) - 20 | Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); 21 | 22 | var bearing = Math.atan2(y, x) / degToRad; 23 | bearing = (bearing + 360.0) % 360.0; 24 | return bearing; 25 | } 26 | 27 | function getLine(pt1, pt2, linerad, drawOptions) { 28 | //returns line through pt1, at right angles to line between pt1 and pt2, length linerad. 29 | //Use Pythogoras- accurate enough on this scale 30 | var latdiff = pt2[0] - pt1[0]; 31 | //need radians for cosine function 32 | var northmean = (pt1[0] + pt2[0]) * Math.PI / 360; 33 | var startrads = pt1[0] * Math.PI / 180; 34 | var longdiff = (pt1[1] - pt2[1]) * Math.cos(northmean); 35 | var hypotenuse = Math.sqrt(latdiff * latdiff + longdiff * longdiff); 36 | //assume earth is a sphere circumference 40030 Km 37 | var latdelta = linerad * longdiff / hypotenuse / 111.1949269; 38 | var longdelta = linerad * latdiff / hypotenuse / 111.1949269 / Math.cos(startrads); 39 | var linestart = L.latLng(pt1[0] - latdelta, pt1[1] - longdelta); 40 | var lineend = L.latLng(pt1[0] + latdelta, longdelta + pt1[1]); 41 | var polylinePoints = [linestart, lineend]; 42 | var polylineOptions = { 43 | color: 'green', 44 | weight: 3, 45 | opacity: 0.8 46 | }; 47 | 48 | return L.polyline(polylinePoints, drawOptions); 49 | } 50 | 51 | function getTpSector(centrept, pt1, pt2, sectorRadius, sectorAngle, drawOptions) { 52 | var headingIn = getBearing(pt1, centrept); 53 | var bearingOut = getBearing(pt2, centrept); 54 | var bisector = headingIn + (bearingOut - headingIn) / 2; 55 | 56 | if (Math.abs(bearingOut - headingIn) > 180) { 57 | bisector = (bisector + 180) % 360; 58 | } 59 | 60 | var beginangle = bisector - sectorAngle / 2; 61 | 62 | if (beginangle < 0) { 63 | beginangle += 360; 64 | } 65 | 66 | var endangle = (bisector + sectorAngle / 2) % 360; 67 | var sectorOptions = jQuery.extend({}, drawOptions, { startAngle: beginangle, stopAngle: endangle }); 68 | return L.circle(centrept, sectorRadius, sectorOptions); 69 | } 70 | 71 | // End of private methods 72 | 73 | var map = L.map(elementName); 74 | 75 | var cartoAttribution = '© OpenStreetMap contributors, © CartoDB'; 76 | var mapLayers = { 77 | positron: L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', { 78 | attribution: cartoAttribution, 79 | maxZoom: 18 80 | }), 81 | 82 | darkMatter: L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', { 83 | attribution: cartoAttribution, 84 | maxZoom: 18 85 | }), 86 | 87 | toner: L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png', { 88 | attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.', 89 | maxZoom: 18 90 | }), 91 | 92 | watercolor: L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png', { 93 | attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.', 94 | maxZoom: 18 95 | }), 96 | terrain: L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png', { 97 | attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.', 98 | maxZoom: 18 99 | }) 100 | }; 101 | 102 | var layersControl = L.control.layers({ 103 | 'Carto Positron': mapLayers.positron, 104 | 'Carto Dark Matter': mapLayers.darkMatter, 105 | 'Stamen Toner': mapLayers.toner, 106 | 'Stamen Watercolor': mapLayers.watercolor, 107 | 'Stamen Terrain': mapLayers.terrain 108 | }); 109 | 110 | mapLayers.terrain.addTo(map); 111 | layersControl.addTo(map); 112 | 113 | var trackLatLong = []; 114 | var timePositionMarker; 115 | L.AwesomeMarkers.Icon.prototype.options.prefix = 'fa'; 116 | var planeIcon = L.AwesomeMarkers.icon({ 117 | icon: 'plane', 118 | iconColor: 'white', 119 | markerColor: 'red' 120 | }); 121 | 122 | return { 123 | reset: function () { 124 | // Clear any existing track data so that a new file can be loaded. 125 | if (mapLayers.track) { 126 | map.removeLayer(mapLayers.track); 127 | layersControl.removeLayer(mapLayers.track); 128 | } 129 | 130 | if (mapLayers.task) { 131 | map.removeLayer(mapLayers.task); 132 | layersControl.removeLayer(mapLayers.task); 133 | } 134 | }, 135 | 136 | addTrack: function (latLong) { 137 | trackLatLong = latLong; 138 | var trackLine = L.polyline(latLong, { color: 'red', weight: 3 }); 139 | timePositionMarker = L.marker(latLong[0], { icon: planeIcon }); 140 | mapLayers.track = L.layerGroup([ 141 | trackLine, 142 | timePositionMarker 143 | ]).addTo(map); 144 | layersControl.addOverlay(mapLayers.track, 'Flight path'); 145 | 146 | map.fitBounds(trackLine.getBounds()); 147 | }, 148 | 149 | addTask: function (coordinates, names) { 150 | //Clearer if we don't show track to and from start line and finish line, as we are going to show lines 151 | var taskLayers = [L.polyline(coordinates, { color: 'blue', weight: 3 })]; 152 | var lineDrawOptions = { 153 | fillColor: 'green', 154 | color: 'black', 155 | weight: 2, 156 | opacity: 0.8 157 | }; 158 | var sectorDrawOptions = { 159 | fillColor: 'green', 160 | fillOpacity: 0.1, 161 | color: 'black', 162 | weight: 1, 163 | opacity: 0.8 164 | }; 165 | //definitions from BGA rules 166 | //defined here as any future changes will be easier 167 | var startLineRadius = 5; 168 | var finishLineRadius = 1; 169 | var tpCircleRadius = 500; 170 | var tpSectorRadius = 20000; 171 | var tpSectorAngle = 90; 172 | var j; 173 | for (j = 0; j < coordinates.length; j++) { 174 | taskLayers.push(L.marker(coordinates[j]).bindPopup(names[j])); 175 | switch (j) { 176 | case 0: 177 | var startline = getLine(coordinates[0], coordinates[1], startLineRadius, lineDrawOptions); 178 | taskLayers.push(startline); 179 | break; 180 | case (coordinates.length - 1): 181 | var finishline = getLine(coordinates[j], coordinates[j - 1], finishLineRadius, lineDrawOptions); 182 | taskLayers.push(finishline); 183 | break; 184 | default: 185 | taskLayers.push(L.circle(coordinates[j], tpCircleRadius, sectorDrawOptions)); 186 | var tpsector = getTpSector(coordinates[j], coordinates[j - 1], coordinates[j + 1], tpSectorRadius, tpSectorAngle, sectorDrawOptions); 187 | taskLayers.push(tpsector); 188 | } 189 | } 190 | mapLayers.task = L.layerGroup(taskLayers).addTo(map); 191 | layersControl.addOverlay(mapLayers.task, 'Task'); 192 | }, 193 | 194 | setTimeMarker: function (timeIndex) { 195 | var markerLatLng = trackLatLong[timeIndex]; 196 | if (markerLatLng) { 197 | timePositionMarker.setLatLng(markerLatLng); 198 | 199 | if (!map.getBounds().contains(markerLatLng)) { 200 | map.panTo(markerLatLng); 201 | } 202 | } 203 | } 204 | }; 205 | } 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /parseigc.js: -------------------------------------------------------------------------------- 1 | // Constructor for an exception which is thrown if the file 2 | // being parsed is not in a valid IGC format. 3 | function IGCException(message) { 4 | 'use strict'; 5 | 6 | this.message = message; 7 | this.name = "IGCException"; 8 | } 9 | 10 | // Parses an IGC logger file. 11 | function parseIGC(igcFile) { 12 | 'use strict'; 13 | 14 | // Looks up the manufacturer name corresponding to 15 | // the three letter code in the first line of the IGC file 16 | // (the 'A' record). 17 | function parseManufacturer(aRecord) { 18 | var manufacturers = { 19 | 'GCS': 'Garrecht', 20 | 'CAM': 'Cambridge Aero Instruments', 21 | 'DSX': 'Data Swan', 22 | 'EWA': 'EW Avionics', 23 | 'FIL': 'Filser', 24 | 'FLA': 'FLARM', 25 | 'SCH': 'Scheffel', 26 | 'ACT': 'Aircotec', 27 | 'NKL': 'Nielsen Kellerman', 28 | 'LXN': 'LX Navigation', 29 | 'IMI': 'IMI Gliding Equipment', 30 | 'NTE': 'New Technologies s.r.l.', 31 | 'PES': 'Peschges', 32 | 'PRT': 'Print Technik', 33 | 'SDI': 'Streamline Data Instruments', 34 | 'TRI': 'Triadis Engineering GmbH', 35 | 'LXV': 'LXNAV d.o.o.', 36 | 'WES': 'Westerboer', 37 | 'XCS': 'XCSoar', 38 | 'XCT': 'XCTrack', 39 | 'ZAN': 'Zander' 40 | }; 41 | 42 | var manufacturerInfo = { 43 | manufacturer: 'Unknown', 44 | serial: aRecord.substring(4, 7) 45 | }; 46 | 47 | var manufacturerCode = aRecord.substring(1, 4); 48 | if (manufacturers[manufacturerCode]) { 49 | manufacturerInfo.manufacturer = manufacturers[manufacturerCode]; 50 | } 51 | 52 | return manufacturerInfo; 53 | } 54 | 55 | // Extracts the flight date from the IGC file. 56 | function extractDate(igcFile) { 57 | // Date is recorded as: HFDTEddmmyy (where HFDTE is a literal and dddmmyy are digits), 58 | // OR in the case of XCTrack the format is: 59 | // HFDTEDATE:150522,01 60 | var dateRecord = igcFile.match(/H[FO]DTE(?:DATE\:)?([\d]{2})([\d]{2})([\d]{2})/); 61 | if (dateRecord === null) { 62 | throw new IGCException('The file does not contain a date header.'); 63 | } 64 | 65 | var day = parseInt(dateRecord[1], 10); 66 | // Javascript numbers months from zero, not 1! 67 | var month = parseInt(dateRecord[2], 10) - 1; 68 | // The IGC specification has a built-in Millennium Bug (2-digit year). 69 | // I will arbitrarily assume that any year before "80" is in the 21st century. 70 | var year = parseInt(dateRecord[3], 10); 71 | 72 | if (year < 80) { 73 | year += 2000; 74 | } else { 75 | year += 1900; 76 | } 77 | return new Date(Date.UTC(year, month, day)); 78 | } 79 | 80 | function parseHeader(headerRecord) { 81 | var headerSubtypes = { 82 | 'PLT': 'Pilot', 83 | 'CM2': 'Crew member 2', 84 | 'GTY': 'Glider type', 85 | 'GID': 'Glider ID', 86 | 'DTM': 'GPS Datum', 87 | 'RFW': 'Firmware version', 88 | 'RHW': 'Hardware version', 89 | 'FTY': 'Flight recorder type', 90 | 'GPS': 'GPS', 91 | 'PRS': 'Pressure sensor', 92 | 'FRS': 'Security suspect, use validation program', 93 | 'CID': 'Competition ID', 94 | 'CCL': 'Competition class' 95 | }; 96 | 97 | var headerName = headerSubtypes[headerRecord.substring(2, 5)]; 98 | if (headerName !== undefined) { 99 | var colonIndex = headerRecord.indexOf(':'); 100 | if (colonIndex !== -1) { 101 | var headerValue = headerRecord.substring(colonIndex + 1); 102 | if (headerValue.length > 0 && /([^\s]+)/.test(headerValue)) { 103 | return { 104 | name: headerName, 105 | value: headerValue 106 | }; 107 | } 108 | } 109 | } 110 | } 111 | 112 | // Parses a latitude and longitude in the form: 113 | // DDMMmmmNDDDMMmmmE 114 | // where M = minutes and m = decimal places of minutes. 115 | function parseLatLong(latLongString) { 116 | var latitude = parseFloat(latLongString.substring(0, 2)) + 117 | parseFloat(latLongString.substring(2, 7)) / 60000.0; 118 | if (latLongString.charAt(7) === 'S') { 119 | latitude = -latitude; 120 | } 121 | 122 | var longitude = parseFloat(latLongString.substring(8, 11)) + 123 | parseFloat(latLongString.substring(11, 16)) / 60000.0; 124 | if (latLongString.charAt(16) === 'W') { 125 | longitude = -longitude; 126 | } 127 | 128 | return [latitude, longitude]; 129 | } 130 | 131 | function parsePosition(positionRecord, model, flightDate) { 132 | // Regex to match position records: 133 | // Hours, minutes, seconds, latitude, N or S, longitude, E or W, 134 | // Fix validity ('A' = 3D fix, 'V' = 2D or no fix), 135 | // pressure altitude, GPS altitude. 136 | // Latitude and longitude are in degrees and minutes, with the minutes 137 | // value multiplied by 1000 so that no decimal point is needed. 138 | // hours minutes seconds latitude longitude press alt gps alt 139 | var positionRegex = /^B([\d]{2})([\d]{2})([\d]{2})([\d]{7}[NS][\d]{8}[EW])([AV])([\d]{5})([\d]{5})/; 140 | var positionMatch = positionRecord.match(positionRegex); 141 | if (positionMatch) { 142 | // Convert the time to a date and time. Start by making a clone of the date 143 | // object that represents the date given in the headers: 144 | var positionTime = new Date(flightDate.getTime()); 145 | positionTime.setUTCHours(parseInt(positionMatch[1], 10), parseInt(positionMatch[2], 10), parseInt(positionMatch[3], 10)); 146 | // If the flight crosses midnight (UTC) then we now have a time that is 24 hours out. 147 | // We know that this is the case if the time is earlier than the first position fix. 148 | if (model.recordTime.length > 0 && 149 | model.recordTime[0] > positionTime) { 150 | positionTime.setDate(flightDate.getDate() + 1); 151 | } 152 | 153 | return { 154 | recordTime: positionTime, 155 | latLong: parseLatLong(positionMatch[4]), 156 | pressureAltitude: parseInt(positionMatch[6], 10), 157 | gpsAltitude: parseInt(positionMatch[7], 10) 158 | }; 159 | } 160 | } 161 | 162 | function parseTask(taskRecord) { 163 | var taskRegex = /^C([\d]{7}[NS][\d]{8}[EW])(.*)/; 164 | var taskMatch = taskRecord.match(taskRegex); 165 | var degreeSymbol = '\u00B0'; 166 | 167 | if (taskMatch) { 168 | var name = taskMatch[2]; 169 | 170 | // If the turnpoint name is blank, use the latitude and longitude. 171 | if (name.trim().length === 0) { 172 | name = taskRecord.substring(1, 3) + 173 | degreeSymbol + 174 | taskRecord.substring(3, 5) + 175 | '.' + 176 | taskRecord.substring(5, 8) + 177 | "' " + 178 | taskRecord.charAt(8) + 179 | ', ' + 180 | taskRecord.substring(9, 12) + 181 | degreeSymbol + 182 | taskRecord.substring(12, 14) + 183 | '.' + 184 | taskRecord.substring(14, 17) + 185 | "' " + 186 | taskRecord.charAt(17); 187 | } 188 | 189 | return { 190 | latLong: parseLatLong(taskMatch[1]), 191 | name: name 192 | }; 193 | } 194 | } 195 | 196 | // ---- Start of IGC parser code ---- 197 | 198 | var invalidFileMessage = 'This does not appear to be an IGC file.'; 199 | var igcLines = igcFile.split('\n'); 200 | if (igcLines.length < 2) { 201 | throw new IGCException(invalidFileMessage); 202 | } 203 | 204 | // Declare the model object that is to be returned; 205 | // this contains the position and altitude data and the header 206 | // values. 207 | var model = { 208 | headers: [], 209 | recordTime: [], 210 | latLong: [], 211 | pressureAltitude: [], 212 | gpsAltitude: [], 213 | task: { 214 | coordinates: [], 215 | names: [], 216 | takeoff: "", 217 | landing: "" 218 | } 219 | }; 220 | 221 | // The first line should begin with 'A' followed by 222 | // a 3-character manufacturer Id and a 3-character serial number. 223 | if (!(/^A[\w]{6}/).test(igcLines[0])) { 224 | throw new IGCException(invalidFileMessage); 225 | } 226 | 227 | var manufacturerInfo = parseManufacturer(igcLines[0]); 228 | model.headers.push({ 229 | name: 'Logger manufacturer', 230 | value: manufacturerInfo.manufacturer 231 | }); 232 | 233 | model.headers.push({ 234 | name: 'Logger serial number', 235 | value: manufacturerInfo.serial 236 | }); 237 | 238 | var flightDate = extractDate(igcFile); 239 | 240 | var lineIndex; 241 | var positionData; 242 | var recordType; 243 | var currentLine; 244 | var turnpoint; // for task declaration lines 245 | var headerData; 246 | for (lineIndex = 0; lineIndex < igcLines.length; lineIndex++) { 247 | currentLine = igcLines[lineIndex]; 248 | recordType = currentLine.charAt(0); 249 | switch (recordType) { 250 | case 'B': // Position fix 251 | positionData = parsePosition(currentLine, model, flightDate); 252 | if (positionData) { 253 | model.recordTime.push(positionData.recordTime); 254 | model.latLong.push(positionData.latLong); 255 | model.pressureAltitude.push(positionData.pressureAltitude); 256 | model.gpsAltitude.push(positionData.gpsAltitude); 257 | } 258 | break; 259 | 260 | case 'C': // Task declaration 261 | turnpoint = parseTask(currentLine); 262 | if (turnpoint) { 263 | model.task.coordinates.push(turnpoint.latLong); 264 | model.task.names.push(turnpoint.name); 265 | } 266 | break; 267 | 268 | case 'H': // Header information 269 | headerData = parseHeader(currentLine); 270 | if (headerData) { 271 | model.headers.push(headerData); 272 | } 273 | break; 274 | } 275 | } 276 | 277 | // Extract takeoff and landing names from model.task and reduce model.task.coordinates to what we want to plot 278 | // Throw away takeoff and landing coordinates as we won't be using them 279 | if(model.task.names.length > 0) { 280 | var takeoffname=model.task.takeoff=model.task.names.shift(); 281 | if (model.task.coordinates[0][0]!==0) { 282 | model.task.takeoff=takeoffname; 283 | } 284 | else { 285 | model.task.takeoff=""; 286 | } 287 | 288 | model.task.coordinates.shift(); 289 | var landingname=model.task.names.pop(); 290 | if (model.task.coordinates[model.task.coordinates.length-1][0]!==0) { 291 | model.task.landing=landingname; 292 | } 293 | else { 294 | model.task.landing=""; 295 | } 296 | model.task.coordinates.pop(); 297 | } 298 | 299 | return model; 300 | } 301 | --------------------------------------------------------------------------------