├── 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 |
32 |
33 | Please enable JavaScript to use this application.
34 |
35 |
36 |
37 | Select a file to view:
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 | Altitude units:
50 |
51 | Metres
52 | Feet
53 |
54 |
55 |
56 |
57 | Time zone:
58 |
59 |
60 |
61 |
62 |
63 |
Flight Information
64 |
65 |
68 |
69 |
74 |
75 |
76 |
77 |
78 |
Time:
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='";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 |
--------------------------------------------------------------------------------