@raruto/leaflet-elevation 2.3.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +325 -323
- package/dist/leaflet-elevation.js +1619 -1575
- package/dist/leaflet-elevation.js.map +1 -1
- package/dist/leaflet-elevation.min.js +1 -1
- package/dist/leaflet-elevation.min.js.map +1 -1
- package/libs/leaflet-gpxgroup.js +346 -340
- package/libs/leaflet-gpxgroup.min.js +1 -1
- package/libs/leaflet-hotline.js +456 -456
- package/package.json +74 -74
- package/src/components/chart.js +783 -783
- package/src/components/d3.js +649 -641
- package/src/components/marker.js +96 -96
- package/src/control.js +1335 -1332
- package/src/handlers/labels.js +45 -0
- package/src/handlers/lineargradient.js +122 -0
- package/src/handlers/runner.js +109 -0
- package/src/handlers/time.js +102 -102
- package/src/options.js +88 -88
- package/src/utils.js +203 -163
package/libs/leaflet-hotline.js
CHANGED
|
@@ -1,457 +1,457 @@
|
|
|
1
|
-
/*
|
|
2
|
-
(c) 2017, iosphere GmbH
|
|
3
|
-
Leaflet.hotline, a Leaflet plugin for drawing gradients along polylines.
|
|
4
|
-
https://github.com/iosphere/Leaflet.hotline/
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
(function (root, plugin) {
|
|
8
|
-
/**
|
|
9
|
-
* UMD wrapper.
|
|
10
|
-
* When used directly in the Browser it expects Leaflet to be globally
|
|
11
|
-
* available as `L`. The plugin then adds itself to Leaflet.
|
|
12
|
-
* When used as a CommonJS module (e.g. with browserify) only the plugin
|
|
13
|
-
* factory gets exported, so one hast to call the factory manually and pass
|
|
14
|
-
* Leaflet as the only parameter.
|
|
15
|
-
* @see {@link https://github.com/umdjs/umd}
|
|
16
|
-
*/
|
|
17
|
-
if (typeof define === 'function' && define.amd) {
|
|
18
|
-
define(['leaflet'], plugin);
|
|
19
|
-
} else if (typeof exports === 'object') {
|
|
20
|
-
module.exports = plugin;
|
|
21
|
-
} else {
|
|
22
|
-
plugin(root.L);
|
|
23
|
-
}
|
|
24
|
-
}(window, function (L) {
|
|
25
|
-
// Plugin is already added to Leaflet
|
|
26
|
-
if (L.Hotline) {
|
|
27
|
-
return L;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Core renderer.
|
|
32
|
-
* @constructor
|
|
33
|
-
* @param {HTMLElement | string} canvas - <canvas> element or its id
|
|
34
|
-
* to initialize the instance on.
|
|
35
|
-
*/
|
|
36
|
-
var Hotline = function (canvas) {
|
|
37
|
-
if (!(this instanceof Hotline)) { return new Hotline(canvas); }
|
|
38
|
-
|
|
39
|
-
var defaultPalette = {
|
|
40
|
-
0.0: 'green',
|
|
41
|
-
0.5: 'yellow',
|
|
42
|
-
1.0: 'red'
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
this._canvas = canvas = ('string' === typeof canvas)
|
|
46
|
-
? document.getElementById(canvas)
|
|
47
|
-
: canvas;
|
|
48
|
-
|
|
49
|
-
this._ctx = canvas.getContext('2d');
|
|
50
|
-
this._width = canvas.width;
|
|
51
|
-
this._height = canvas.height;
|
|
52
|
-
|
|
53
|
-
this._weight = 5;
|
|
54
|
-
this._outlineWidth = 1;
|
|
55
|
-
this._outlineColor = 'black';
|
|
56
|
-
|
|
57
|
-
this._min = 0;
|
|
58
|
-
this._max = 1;
|
|
59
|
-
|
|
60
|
-
this._data = [];
|
|
61
|
-
|
|
62
|
-
this.palette(defaultPalette);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
Hotline.prototype = {
|
|
66
|
-
/**
|
|
67
|
-
* Sets the width of the canvas. Used when clearing the canvas.
|
|
68
|
-
* @param {number} width - Width of the canvas.
|
|
69
|
-
*/
|
|
70
|
-
width: function (width) {
|
|
71
|
-
this._width = width;
|
|
72
|
-
return this;
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Sets the height of the canvas. Used when clearing the canvas.
|
|
77
|
-
* @param {number} height - Height of the canvas.
|
|
78
|
-
*/
|
|
79
|
-
height: function (height) {
|
|
80
|
-
this._height = height;
|
|
81
|
-
return this;
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Sets the weight of the path.
|
|
86
|
-
* @param {number} weight - Weight of the path in px.
|
|
87
|
-
*/
|
|
88
|
-
weight: function (weight) {
|
|
89
|
-
this._weight = weight;
|
|
90
|
-
return this;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Sets the width of the outline around the path.
|
|
95
|
-
* @param {number} outlineWidth - Width of the outline in px.
|
|
96
|
-
*/
|
|
97
|
-
outlineWidth: function (outlineWidth) {
|
|
98
|
-
this._outlineWidth = outlineWidth;
|
|
99
|
-
return this;
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Sets the color of the outline around the path.
|
|
104
|
-
* @param {string} outlineColor - A CSS color value.
|
|
105
|
-
*/
|
|
106
|
-
outlineColor: function (outlineColor) {
|
|
107
|
-
this._outlineColor = outlineColor;
|
|
108
|
-
return this;
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Sets the palette gradient.
|
|
113
|
-
* @param {Object.<number, string>} palette - Gradient definition.
|
|
114
|
-
* e.g. { 0.0: 'white', 1.0: 'black' }
|
|
115
|
-
*/
|
|
116
|
-
palette: function (palette) {
|
|
117
|
-
var canvas = document.createElement('canvas'),
|
|
118
|
-
ctx = canvas.getContext('2d'),
|
|
119
|
-
gradient = ctx.createLinearGradient(0, 0, 0, 256);
|
|
120
|
-
|
|
121
|
-
canvas.width = 1;
|
|
122
|
-
canvas.height = 256;
|
|
123
|
-
|
|
124
|
-
for (var i in palette) {
|
|
125
|
-
gradient.addColorStop(i, palette[i]);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
ctx.fillStyle = gradient;
|
|
129
|
-
ctx.fillRect(0, 0, 1, 256);
|
|
130
|
-
|
|
131
|
-
this._palette = ctx.getImageData(0, 0, 1, 256).data;
|
|
132
|
-
|
|
133
|
-
return this;
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Sets the value used at the start of the palette gradient.
|
|
138
|
-
* @param {number} min
|
|
139
|
-
*/
|
|
140
|
-
min: function (min) {
|
|
141
|
-
this._min = min;
|
|
142
|
-
return this;
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Sets the value used at the end of the palette gradient.
|
|
147
|
-
* @param {number} max
|
|
148
|
-
*/
|
|
149
|
-
max: function (max) {
|
|
150
|
-
this._max = max;
|
|
151
|
-
return this;
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* A path to rander as a hotline.
|
|
156
|
-
* @typedef Array.<{x:number, y:number, z:number}> Path - Array of x, y and z coordinates.
|
|
157
|
-
*/
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Sets the data that gets drawn on the canvas.
|
|
161
|
-
* @param {(Path|Path[])} data - A single path or an array of paths.
|
|
162
|
-
*/
|
|
163
|
-
data: function (data) {
|
|
164
|
-
this._data = data;
|
|
165
|
-
return this;
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Adds a path to the list of paths.
|
|
170
|
-
* @param {Path} path
|
|
171
|
-
*/
|
|
172
|
-
add: function (path) {
|
|
173
|
-
this._data.push(path);
|
|
174
|
-
return this;
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Draws the currently set paths.
|
|
179
|
-
*/
|
|
180
|
-
draw: function () {
|
|
181
|
-
var ctx = this._ctx;
|
|
182
|
-
|
|
183
|
-
ctx.globalCompositeOperation = 'source-over';
|
|
184
|
-
ctx.lineCap = 'round';
|
|
185
|
-
|
|
186
|
-
this._drawOutline(ctx);
|
|
187
|
-
this._drawHotline(ctx);
|
|
188
|
-
|
|
189
|
-
return this;
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Gets the RGB values of a given z value of the current palette.
|
|
194
|
-
* @param {number} value - Value to get the color for, should be between min and max.
|
|
195
|
-
* @returns {Array.<number>} The RGB values as an array [r, g, b]
|
|
196
|
-
*/
|
|
197
|
-
getRGBForValue: function (value) {
|
|
198
|
-
var valueRelative = Math.min(Math.max((value - this._min) / (this._max - this._min), 0), 0.999);
|
|
199
|
-
var paletteIndex = Math.floor(valueRelative * 256) * 4;
|
|
200
|
-
|
|
201
|
-
return [
|
|
202
|
-
this._palette[paletteIndex],
|
|
203
|
-
this._palette[paletteIndex + 1],
|
|
204
|
-
this._palette[paletteIndex + 2]
|
|
205
|
-
];
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Draws the outline of the graphs.
|
|
210
|
-
* @private
|
|
211
|
-
*/
|
|
212
|
-
_drawOutline: function (ctx) {
|
|
213
|
-
var i, j, dataLength, path, pathLength, pointStart, pointEnd;
|
|
214
|
-
|
|
215
|
-
if (this._outlineWidth) {
|
|
216
|
-
for (i = 0, dataLength = this._data.length; i < dataLength; i++) {
|
|
217
|
-
path = this._data[i];
|
|
218
|
-
ctx.lineWidth = this._weight + 2 * this._outlineWidth;
|
|
219
|
-
|
|
220
|
-
for (j = 1, pathLength = path.length; j < pathLength; j++) {
|
|
221
|
-
pointStart = path[j - 1];
|
|
222
|
-
pointEnd = path[j];
|
|
223
|
-
|
|
224
|
-
ctx.strokeStyle = this._outlineColor;
|
|
225
|
-
ctx.beginPath();
|
|
226
|
-
ctx.moveTo(pointStart.x, pointStart.y);
|
|
227
|
-
ctx.lineTo(pointEnd.x, pointEnd.y);
|
|
228
|
-
ctx.stroke();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Draws the color encoded hotline of the graphs.
|
|
236
|
-
* @private
|
|
237
|
-
*/
|
|
238
|
-
_drawHotline: function (ctx) {
|
|
239
|
-
var i, j, dataLength, path, pathLength, pointStart, pointEnd,
|
|
240
|
-
gradient, gradientStartRGB, gradientEndRGB;
|
|
241
|
-
|
|
242
|
-
ctx.lineWidth = this._weight;
|
|
243
|
-
|
|
244
|
-
for (i = 0, dataLength = this._data.length; i < dataLength; i++) {
|
|
245
|
-
path = this._data[i];
|
|
246
|
-
|
|
247
|
-
for (j = 1, pathLength = path.length; j < pathLength; j++) {
|
|
248
|
-
pointStart = path[j - 1];
|
|
249
|
-
pointEnd = path[j];
|
|
250
|
-
|
|
251
|
-
// Create a gradient for each segment, pick start end end colors from palette gradient
|
|
252
|
-
gradient = ctx.createLinearGradient(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y);
|
|
253
|
-
gradientStartRGB = this.getRGBForValue(pointStart.z);
|
|
254
|
-
gradientEndRGB = this.getRGBForValue(pointEnd.z);
|
|
255
|
-
gradient.addColorStop(0, 'rgb(' + gradientStartRGB.join(',') + ')');
|
|
256
|
-
gradient.addColorStop(1, 'rgb(' + gradientEndRGB.join(',') + ')');
|
|
257
|
-
|
|
258
|
-
ctx.strokeStyle = gradient;
|
|
259
|
-
ctx.beginPath();
|
|
260
|
-
ctx.moveTo(pointStart.x, pointStart.y);
|
|
261
|
-
ctx.lineTo(pointEnd.x, pointEnd.y);
|
|
262
|
-
ctx.stroke();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
var Renderer = L.Canvas.extend({
|
|
270
|
-
_initContainer: function () {
|
|
271
|
-
L.Canvas.prototype._initContainer.call(this);
|
|
272
|
-
this._hotline = new Hotline(this._container);
|
|
273
|
-
},
|
|
274
|
-
|
|
275
|
-
_update: function () {
|
|
276
|
-
L.Canvas.prototype._update.call(this);
|
|
277
|
-
this._hotline.width(this._container.width);
|
|
278
|
-
this._hotline.height(this._container.height);
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
_updatePoly: function (layer) {
|
|
282
|
-
if (!this._drawing) { return; }
|
|
283
|
-
|
|
284
|
-
var parts = layer._parts;
|
|
285
|
-
|
|
286
|
-
if (!parts.length) { return; }
|
|
287
|
-
|
|
288
|
-
this._updateOptions(layer);
|
|
289
|
-
|
|
290
|
-
this._hotline
|
|
291
|
-
.data(parts)
|
|
292
|
-
.draw();
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
_updateOptions: function (layer) {
|
|
296
|
-
if (layer.options.min != null) {
|
|
297
|
-
this._hotline.min(layer.options.min);
|
|
298
|
-
}
|
|
299
|
-
if (layer.options.max != null) {
|
|
300
|
-
this._hotline.max(layer.options.max);
|
|
301
|
-
}
|
|
302
|
-
if (layer.options.weight != null) {
|
|
303
|
-
this._hotline.weight(layer.options.weight);
|
|
304
|
-
}
|
|
305
|
-
if (layer.options.outlineWidth != null) {
|
|
306
|
-
this._hotline.outlineWidth(layer.options.outlineWidth);
|
|
307
|
-
}
|
|
308
|
-
if (layer.options.outlineColor != null) {
|
|
309
|
-
this._hotline.outlineColor(layer.options.outlineColor);
|
|
310
|
-
}
|
|
311
|
-
if (layer.options.palette) {
|
|
312
|
-
this._hotline.palette(layer.options.palette);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
var renderer = function (options) {
|
|
318
|
-
return L.Browser.canvas ? new Renderer(options) : null;
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
var Util = {
|
|
323
|
-
/**
|
|
324
|
-
* This is just a copy of the original Leaflet version that support a third z coordinate.
|
|
325
|
-
* @see {@link http://leafletjs.com/reference.html#lineutil-clipsegment|Leaflet}
|
|
326
|
-
*/
|
|
327
|
-
clipSegment: function (a, b, bounds, useLastCode, round) {
|
|
328
|
-
var codeA = useLastCode ? this._lastCode : L.LineUtil._getBitCode(a, bounds),
|
|
329
|
-
codeB = L.LineUtil._getBitCode(b, bounds),
|
|
330
|
-
codeOut, p, newCode;
|
|
331
|
-
|
|
332
|
-
// save 2nd code to avoid calculating it on the next segment
|
|
333
|
-
this._lastCode = codeB;
|
|
334
|
-
|
|
335
|
-
while (true) {
|
|
336
|
-
// if a,b is inside the clip window (trivial accept)
|
|
337
|
-
if (!(codeA | codeB)) {
|
|
338
|
-
return [a, b];
|
|
339
|
-
// if a,b is outside the clip window (trivial reject)
|
|
340
|
-
} else if (codeA & codeB) {
|
|
341
|
-
return false;
|
|
342
|
-
// other cases
|
|
343
|
-
} else {
|
|
344
|
-
codeOut = codeA || codeB;
|
|
345
|
-
p = L.LineUtil._getEdgeIntersection(a, b, codeOut, bounds, round);
|
|
346
|
-
newCode = L.LineUtil._getBitCode(p, bounds);
|
|
347
|
-
|
|
348
|
-
if (codeOut === codeA) {
|
|
349
|
-
p.z = a.z;
|
|
350
|
-
a = p;
|
|
351
|
-
codeA = newCode;
|
|
352
|
-
} else {
|
|
353
|
-
p.z = b.z;
|
|
354
|
-
b = p;
|
|
355
|
-
codeB = newCode;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
L.Hotline = L.Polyline.extend({
|
|
364
|
-
statics: {
|
|
365
|
-
Renderer: Renderer,
|
|
366
|
-
renderer: renderer
|
|
367
|
-
},
|
|
368
|
-
|
|
369
|
-
options: {
|
|
370
|
-
renderer: renderer(),
|
|
371
|
-
min: 0,
|
|
372
|
-
max: 1,
|
|
373
|
-
palette: {
|
|
374
|
-
0.0: 'green',
|
|
375
|
-
0.5: 'yellow',
|
|
376
|
-
1.0: 'red'
|
|
377
|
-
},
|
|
378
|
-
weight: 5,
|
|
379
|
-
outlineColor: 'black',
|
|
380
|
-
outlineWidth: 1
|
|
381
|
-
},
|
|
382
|
-
|
|
383
|
-
getRGBForValue: function (value) {
|
|
384
|
-
return this._renderer._hotline.getRGBForValue(value);
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Just like the Leaflet version, but with support for a z coordinate.
|
|
389
|
-
*/
|
|
390
|
-
_projectLatlngs: function (latlngs, result, projectedBounds) {
|
|
391
|
-
var flat = latlngs[0] instanceof L.LatLng,
|
|
392
|
-
len = latlngs.length,
|
|
393
|
-
i, ring;
|
|
394
|
-
|
|
395
|
-
if (flat) {
|
|
396
|
-
ring = [];
|
|
397
|
-
for (i = 0; i < len; i++) {
|
|
398
|
-
ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
|
|
399
|
-
// Add the altitude of the latLng as the z coordinate to the point
|
|
400
|
-
ring[i].z = latlngs[i].alt;
|
|
401
|
-
projectedBounds.extend(ring[i]);
|
|
402
|
-
}
|
|
403
|
-
result.push(ring);
|
|
404
|
-
} else {
|
|
405
|
-
for (i = 0; i < len; i++) {
|
|
406
|
-
this._projectLatlngs(latlngs[i], result, projectedBounds);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Just like the Leaflet version, but uses `Util.clipSegment()`.
|
|
413
|
-
*/
|
|
414
|
-
_clipPoints: function () {
|
|
415
|
-
if (this.options.noClip) {
|
|
416
|
-
this._parts = this._rings;
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
this._parts = [];
|
|
421
|
-
|
|
422
|
-
var parts = this._parts,
|
|
423
|
-
bounds = this._renderer._bounds,
|
|
424
|
-
i, j, k, len, len2, segment, points;
|
|
425
|
-
|
|
426
|
-
for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
|
|
427
|
-
points = this._rings[i];
|
|
428
|
-
|
|
429
|
-
for (j = 0, len2 = points.length; j < len2 - 1; j++) {
|
|
430
|
-
segment = Util.clipSegment(points[j], points[j + 1], bounds, j, true);
|
|
431
|
-
|
|
432
|
-
if (!segment) { continue; }
|
|
433
|
-
|
|
434
|
-
parts[k] = parts[k] || [];
|
|
435
|
-
parts[k].push(segment[0]);
|
|
436
|
-
|
|
437
|
-
// if segment goes out of screen, or it's the last one, it's the end of the line part
|
|
438
|
-
if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
|
|
439
|
-
parts[k].push(segment[1]);
|
|
440
|
-
k++;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
},
|
|
445
|
-
|
|
446
|
-
_clickTolerance: function () {
|
|
447
|
-
return this.options.weight / 2 + this.options.outlineWidth + (L.Browser.touch ? 10 : 0);
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
L.hotline = function (latlngs, options) {
|
|
452
|
-
return new L.Hotline(latlngs, options);
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return L;
|
|
1
|
+
/*
|
|
2
|
+
(c) 2017, iosphere GmbH
|
|
3
|
+
Leaflet.hotline, a Leaflet plugin for drawing gradients along polylines.
|
|
4
|
+
https://github.com/iosphere/Leaflet.hotline/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (root, plugin) {
|
|
8
|
+
/**
|
|
9
|
+
* UMD wrapper.
|
|
10
|
+
* When used directly in the Browser it expects Leaflet to be globally
|
|
11
|
+
* available as `L`. The plugin then adds itself to Leaflet.
|
|
12
|
+
* When used as a CommonJS module (e.g. with browserify) only the plugin
|
|
13
|
+
* factory gets exported, so one hast to call the factory manually and pass
|
|
14
|
+
* Leaflet as the only parameter.
|
|
15
|
+
* @see {@link https://github.com/umdjs/umd}
|
|
16
|
+
*/
|
|
17
|
+
if (typeof define === 'function' && define.amd) {
|
|
18
|
+
define(['leaflet'], plugin);
|
|
19
|
+
} else if (typeof exports === 'object') {
|
|
20
|
+
module.exports = plugin;
|
|
21
|
+
} else {
|
|
22
|
+
plugin(root.L);
|
|
23
|
+
}
|
|
24
|
+
}(window, function (L) {
|
|
25
|
+
// Plugin is already added to Leaflet
|
|
26
|
+
if (L.Hotline) {
|
|
27
|
+
return L;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Core renderer.
|
|
32
|
+
* @constructor
|
|
33
|
+
* @param {HTMLElement | string} canvas - <canvas> element or its id
|
|
34
|
+
* to initialize the instance on.
|
|
35
|
+
*/
|
|
36
|
+
var Hotline = function (canvas) {
|
|
37
|
+
if (!(this instanceof Hotline)) { return new Hotline(canvas); }
|
|
38
|
+
|
|
39
|
+
var defaultPalette = {
|
|
40
|
+
0.0: 'green',
|
|
41
|
+
0.5: 'yellow',
|
|
42
|
+
1.0: 'red'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this._canvas = canvas = ('string' === typeof canvas)
|
|
46
|
+
? document.getElementById(canvas)
|
|
47
|
+
: canvas;
|
|
48
|
+
|
|
49
|
+
this._ctx = canvas.getContext('2d');
|
|
50
|
+
this._width = canvas.width;
|
|
51
|
+
this._height = canvas.height;
|
|
52
|
+
|
|
53
|
+
this._weight = 5;
|
|
54
|
+
this._outlineWidth = 1;
|
|
55
|
+
this._outlineColor = 'black';
|
|
56
|
+
|
|
57
|
+
this._min = 0;
|
|
58
|
+
this._max = 1;
|
|
59
|
+
|
|
60
|
+
this._data = [];
|
|
61
|
+
|
|
62
|
+
this.palette(defaultPalette);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
Hotline.prototype = {
|
|
66
|
+
/**
|
|
67
|
+
* Sets the width of the canvas. Used when clearing the canvas.
|
|
68
|
+
* @param {number} width - Width of the canvas.
|
|
69
|
+
*/
|
|
70
|
+
width: function (width) {
|
|
71
|
+
this._width = width;
|
|
72
|
+
return this;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sets the height of the canvas. Used when clearing the canvas.
|
|
77
|
+
* @param {number} height - Height of the canvas.
|
|
78
|
+
*/
|
|
79
|
+
height: function (height) {
|
|
80
|
+
this._height = height;
|
|
81
|
+
return this;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets the weight of the path.
|
|
86
|
+
* @param {number} weight - Weight of the path in px.
|
|
87
|
+
*/
|
|
88
|
+
weight: function (weight) {
|
|
89
|
+
this._weight = weight;
|
|
90
|
+
return this;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Sets the width of the outline around the path.
|
|
95
|
+
* @param {number} outlineWidth - Width of the outline in px.
|
|
96
|
+
*/
|
|
97
|
+
outlineWidth: function (outlineWidth) {
|
|
98
|
+
this._outlineWidth = outlineWidth;
|
|
99
|
+
return this;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Sets the color of the outline around the path.
|
|
104
|
+
* @param {string} outlineColor - A CSS color value.
|
|
105
|
+
*/
|
|
106
|
+
outlineColor: function (outlineColor) {
|
|
107
|
+
this._outlineColor = outlineColor;
|
|
108
|
+
return this;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sets the palette gradient.
|
|
113
|
+
* @param {Object.<number, string>} palette - Gradient definition.
|
|
114
|
+
* e.g. { 0.0: 'white', 1.0: 'black' }
|
|
115
|
+
*/
|
|
116
|
+
palette: function (palette) {
|
|
117
|
+
var canvas = document.createElement('canvas'),
|
|
118
|
+
ctx = canvas.getContext('2d'),
|
|
119
|
+
gradient = ctx.createLinearGradient(0, 0, 0, 256);
|
|
120
|
+
|
|
121
|
+
canvas.width = 1;
|
|
122
|
+
canvas.height = 256;
|
|
123
|
+
|
|
124
|
+
for (var i in palette) {
|
|
125
|
+
gradient.addColorStop(i, palette[i]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ctx.fillStyle = gradient;
|
|
129
|
+
ctx.fillRect(0, 0, 1, 256);
|
|
130
|
+
|
|
131
|
+
this._palette = ctx.getImageData(0, 0, 1, 256).data;
|
|
132
|
+
|
|
133
|
+
return this;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets the value used at the start of the palette gradient.
|
|
138
|
+
* @param {number} min
|
|
139
|
+
*/
|
|
140
|
+
min: function (min) {
|
|
141
|
+
this._min = min;
|
|
142
|
+
return this;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Sets the value used at the end of the palette gradient.
|
|
147
|
+
* @param {number} max
|
|
148
|
+
*/
|
|
149
|
+
max: function (max) {
|
|
150
|
+
this._max = max;
|
|
151
|
+
return this;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A path to rander as a hotline.
|
|
156
|
+
* @typedef Array.<{x:number, y:number, z:number}> Path - Array of x, y and z coordinates.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sets the data that gets drawn on the canvas.
|
|
161
|
+
* @param {(Path|Path[])} data - A single path or an array of paths.
|
|
162
|
+
*/
|
|
163
|
+
data: function (data) {
|
|
164
|
+
this._data = data;
|
|
165
|
+
return this;
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Adds a path to the list of paths.
|
|
170
|
+
* @param {Path} path
|
|
171
|
+
*/
|
|
172
|
+
add: function (path) {
|
|
173
|
+
this._data.push(path);
|
|
174
|
+
return this;
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Draws the currently set paths.
|
|
179
|
+
*/
|
|
180
|
+
draw: function () {
|
|
181
|
+
var ctx = this._ctx;
|
|
182
|
+
|
|
183
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
184
|
+
ctx.lineCap = 'round';
|
|
185
|
+
|
|
186
|
+
this._drawOutline(ctx);
|
|
187
|
+
this._drawHotline(ctx);
|
|
188
|
+
|
|
189
|
+
return this;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Gets the RGB values of a given z value of the current palette.
|
|
194
|
+
* @param {number} value - Value to get the color for, should be between min and max.
|
|
195
|
+
* @returns {Array.<number>} The RGB values as an array [r, g, b]
|
|
196
|
+
*/
|
|
197
|
+
getRGBForValue: function (value) {
|
|
198
|
+
var valueRelative = Math.min(Math.max((value - this._min) / (this._max - this._min), 0), 0.999);
|
|
199
|
+
var paletteIndex = Math.floor(valueRelative * 256) * 4;
|
|
200
|
+
|
|
201
|
+
return [
|
|
202
|
+
this._palette[paletteIndex],
|
|
203
|
+
this._palette[paletteIndex + 1],
|
|
204
|
+
this._palette[paletteIndex + 2]
|
|
205
|
+
];
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Draws the outline of the graphs.
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
_drawOutline: function (ctx) {
|
|
213
|
+
var i, j, dataLength, path, pathLength, pointStart, pointEnd;
|
|
214
|
+
|
|
215
|
+
if (this._outlineWidth) {
|
|
216
|
+
for (i = 0, dataLength = this._data.length; i < dataLength; i++) {
|
|
217
|
+
path = this._data[i];
|
|
218
|
+
ctx.lineWidth = this._weight + 2 * this._outlineWidth;
|
|
219
|
+
|
|
220
|
+
for (j = 1, pathLength = path.length; j < pathLength; j++) {
|
|
221
|
+
pointStart = path[j - 1];
|
|
222
|
+
pointEnd = path[j];
|
|
223
|
+
|
|
224
|
+
ctx.strokeStyle = this._outlineColor;
|
|
225
|
+
ctx.beginPath();
|
|
226
|
+
ctx.moveTo(pointStart.x, pointStart.y);
|
|
227
|
+
ctx.lineTo(pointEnd.x, pointEnd.y);
|
|
228
|
+
ctx.stroke();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Draws the color encoded hotline of the graphs.
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
_drawHotline: function (ctx) {
|
|
239
|
+
var i, j, dataLength, path, pathLength, pointStart, pointEnd,
|
|
240
|
+
gradient, gradientStartRGB, gradientEndRGB;
|
|
241
|
+
|
|
242
|
+
ctx.lineWidth = this._weight;
|
|
243
|
+
|
|
244
|
+
for (i = 0, dataLength = this._data.length; i < dataLength; i++) {
|
|
245
|
+
path = this._data[i];
|
|
246
|
+
|
|
247
|
+
for (j = 1, pathLength = path.length; j < pathLength; j++) {
|
|
248
|
+
pointStart = path[j - 1];
|
|
249
|
+
pointEnd = path[j];
|
|
250
|
+
|
|
251
|
+
// Create a gradient for each segment, pick start end end colors from palette gradient
|
|
252
|
+
gradient = ctx.createLinearGradient(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y);
|
|
253
|
+
gradientStartRGB = this.getRGBForValue(pointStart.z);
|
|
254
|
+
gradientEndRGB = this.getRGBForValue(pointEnd.z);
|
|
255
|
+
gradient.addColorStop(0, 'rgb(' + gradientStartRGB.join(',') + ')');
|
|
256
|
+
gradient.addColorStop(1, 'rgb(' + gradientEndRGB.join(',') + ')');
|
|
257
|
+
|
|
258
|
+
ctx.strokeStyle = gradient;
|
|
259
|
+
ctx.beginPath();
|
|
260
|
+
ctx.moveTo(pointStart.x, pointStart.y);
|
|
261
|
+
ctx.lineTo(pointEnd.x, pointEnd.y);
|
|
262
|
+
ctx.stroke();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
var Renderer = L.Canvas.extend({
|
|
270
|
+
_initContainer: function () {
|
|
271
|
+
L.Canvas.prototype._initContainer.call(this);
|
|
272
|
+
this._hotline = new Hotline(this._container);
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
_update: function () {
|
|
276
|
+
L.Canvas.prototype._update.call(this);
|
|
277
|
+
this._hotline.width(this._container.width);
|
|
278
|
+
this._hotline.height(this._container.height);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
_updatePoly: function (layer) {
|
|
282
|
+
if (!this._drawing) { return; }
|
|
283
|
+
|
|
284
|
+
var parts = layer._parts;
|
|
285
|
+
|
|
286
|
+
if (!parts.length) { return; }
|
|
287
|
+
|
|
288
|
+
this._updateOptions(layer);
|
|
289
|
+
|
|
290
|
+
this._hotline
|
|
291
|
+
.data(parts)
|
|
292
|
+
.draw();
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
_updateOptions: function (layer) {
|
|
296
|
+
if (layer.options.min != null) {
|
|
297
|
+
this._hotline.min(layer.options.min);
|
|
298
|
+
}
|
|
299
|
+
if (layer.options.max != null) {
|
|
300
|
+
this._hotline.max(layer.options.max);
|
|
301
|
+
}
|
|
302
|
+
if (layer.options.weight != null) {
|
|
303
|
+
this._hotline.weight(layer.options.weight);
|
|
304
|
+
}
|
|
305
|
+
if (layer.options.outlineWidth != null) {
|
|
306
|
+
this._hotline.outlineWidth(layer.options.outlineWidth);
|
|
307
|
+
}
|
|
308
|
+
if (layer.options.outlineColor != null) {
|
|
309
|
+
this._hotline.outlineColor(layer.options.outlineColor);
|
|
310
|
+
}
|
|
311
|
+
if (layer.options.palette) {
|
|
312
|
+
this._hotline.palette(layer.options.palette);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
var renderer = function (options) {
|
|
318
|
+
return L.Browser.canvas ? new Renderer(options) : null;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
var Util = {
|
|
323
|
+
/**
|
|
324
|
+
* This is just a copy of the original Leaflet version that support a third z coordinate.
|
|
325
|
+
* @see {@link http://leafletjs.com/reference.html#lineutil-clipsegment|Leaflet}
|
|
326
|
+
*/
|
|
327
|
+
clipSegment: function (a, b, bounds, useLastCode, round) {
|
|
328
|
+
var codeA = useLastCode ? this._lastCode : L.LineUtil._getBitCode(a, bounds),
|
|
329
|
+
codeB = L.LineUtil._getBitCode(b, bounds),
|
|
330
|
+
codeOut, p, newCode;
|
|
331
|
+
|
|
332
|
+
// save 2nd code to avoid calculating it on the next segment
|
|
333
|
+
this._lastCode = codeB;
|
|
334
|
+
|
|
335
|
+
while (true) {
|
|
336
|
+
// if a,b is inside the clip window (trivial accept)
|
|
337
|
+
if (!(codeA | codeB)) {
|
|
338
|
+
return [a, b];
|
|
339
|
+
// if a,b is outside the clip window (trivial reject)
|
|
340
|
+
} else if (codeA & codeB) {
|
|
341
|
+
return false;
|
|
342
|
+
// other cases
|
|
343
|
+
} else {
|
|
344
|
+
codeOut = codeA || codeB;
|
|
345
|
+
p = L.LineUtil._getEdgeIntersection(a, b, codeOut, bounds, round);
|
|
346
|
+
newCode = L.LineUtil._getBitCode(p, bounds);
|
|
347
|
+
|
|
348
|
+
if (codeOut === codeA) {
|
|
349
|
+
p.z = a.z;
|
|
350
|
+
a = p;
|
|
351
|
+
codeA = newCode;
|
|
352
|
+
} else {
|
|
353
|
+
p.z = b.z;
|
|
354
|
+
b = p;
|
|
355
|
+
codeB = newCode;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
L.Hotline = L.Polyline.extend({
|
|
364
|
+
statics: {
|
|
365
|
+
Renderer: Renderer,
|
|
366
|
+
renderer: renderer
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
options: {
|
|
370
|
+
renderer: renderer(),
|
|
371
|
+
min: 0,
|
|
372
|
+
max: 1,
|
|
373
|
+
palette: {
|
|
374
|
+
0.0: 'green',
|
|
375
|
+
0.5: 'yellow',
|
|
376
|
+
1.0: 'red'
|
|
377
|
+
},
|
|
378
|
+
weight: 5,
|
|
379
|
+
outlineColor: 'black',
|
|
380
|
+
outlineWidth: 1
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
getRGBForValue: function (value) {
|
|
384
|
+
return this._renderer._hotline.getRGBForValue(value);
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Just like the Leaflet version, but with support for a z coordinate.
|
|
389
|
+
*/
|
|
390
|
+
_projectLatlngs: function (latlngs, result, projectedBounds) {
|
|
391
|
+
var flat = latlngs[0] instanceof L.LatLng,
|
|
392
|
+
len = latlngs.length,
|
|
393
|
+
i, ring;
|
|
394
|
+
|
|
395
|
+
if (flat) {
|
|
396
|
+
ring = [];
|
|
397
|
+
for (i = 0; i < len; i++) {
|
|
398
|
+
ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
|
|
399
|
+
// Add the altitude of the latLng as the z coordinate to the point
|
|
400
|
+
ring[i].z = latlngs[i].alt;
|
|
401
|
+
projectedBounds.extend(ring[i]);
|
|
402
|
+
}
|
|
403
|
+
result.push(ring);
|
|
404
|
+
} else {
|
|
405
|
+
for (i = 0; i < len; i++) {
|
|
406
|
+
this._projectLatlngs(latlngs[i], result, projectedBounds);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Just like the Leaflet version, but uses `Util.clipSegment()`.
|
|
413
|
+
*/
|
|
414
|
+
_clipPoints: function () {
|
|
415
|
+
if (this.options.noClip) {
|
|
416
|
+
this._parts = this._rings;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this._parts = [];
|
|
421
|
+
|
|
422
|
+
var parts = this._parts,
|
|
423
|
+
bounds = this._renderer._bounds,
|
|
424
|
+
i, j, k, len, len2, segment, points;
|
|
425
|
+
|
|
426
|
+
for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
|
|
427
|
+
points = this._rings[i];
|
|
428
|
+
|
|
429
|
+
for (j = 0, len2 = points.length; j < len2 - 1; j++) {
|
|
430
|
+
segment = Util.clipSegment(points[j], points[j + 1], bounds, j, true);
|
|
431
|
+
|
|
432
|
+
if (!segment) { continue; }
|
|
433
|
+
|
|
434
|
+
parts[k] = parts[k] || [];
|
|
435
|
+
parts[k].push(segment[0]);
|
|
436
|
+
|
|
437
|
+
// if segment goes out of screen, or it's the last one, it's the end of the line part
|
|
438
|
+
if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
|
|
439
|
+
parts[k].push(segment[1]);
|
|
440
|
+
k++;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
_clickTolerance: function () {
|
|
447
|
+
return this.options.weight / 2 + this.options.outlineWidth + (L.Browser.touch ? 10 : 0);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
L.hotline = function (latlngs, options) {
|
|
452
|
+
return new L.Hotline(latlngs, options);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
return L;
|
|
457
457
|
}));
|