diff options
| author | Dennis Fink | 2026-07-23 00:18:21 +0200 |
|---|---|---|
| committer | Dennis Fink | 2026-07-23 00:18:21 +0200 |
| commit | 1fe1d13c600b7dfe421b3b99b833b7d5d1232724 (patch) | |
| tree | 2e8636160cc1719c907be56f5ef8cbc7e0f10a53 /assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js | |
| parent | ca77de06be474dfd712402c327dc436864d233a3 (diff) | |
| download | dennisfink.me-1fe1d13c600b7dfe421b3b99b833b7d5d1232724.tar.gz dennisfink.me-1fe1d13c600b7dfe421b3b99b833b7d5d1232724.zip | |
Add GPX trail maps to blog posts
This adds a trail flow block so posts can include attached GPX files as
interactive maps with route display, elevation charts, activity
summaries, and start/finish markers.
The trail assets are loaded only when needed, keeping the shared
templates clean while vendoring the Leaflet dependencies required for
offline, self-hosted map rendering.
Diffstat (limited to '')
| -rw-r--r-- | assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js b/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js new file mode 100644 index 0000000..1684560 --- /dev/null +++ b/assets/static/vendor/leaflet-elevation/libs/leaflet-hotline.js @@ -0,0 +1,457 @@ +/* +(c) 2017, iosphere GmbH +Leaflet.hotline, a Leaflet plugin for drawing gradients along polylines. +https://github.com/iosphere/Leaflet.hotline/ +*/ + +(function (root, plugin) { + /** + * UMD wrapper. + * When used directly in the Browser it expects Leaflet to be globally + * available as `L`. The plugin then adds itself to Leaflet. + * When used as a CommonJS module (e.g. with browserify) only the plugin + * factory gets exported, so one hast to call the factory manually and pass + * Leaflet as the only parameter. + * @see {@link https://github.com/umdjs/umd} + */ + if (typeof define === 'function' && define.amd) { + define(['leaflet'], plugin); + } else if (typeof exports === 'object') { + module.exports = plugin; + } else { + plugin(root.L); + } +}(window, function (L) { + // Plugin is already added to Leaflet + if (L.Hotline) { + return L; + } + + /** + * Core renderer. + * @constructor + * @param {HTMLElement | string} canvas - <canvas> element or its id + * to initialize the instance on. + */ + var Hotline = function (canvas) { + if (!(this instanceof Hotline)) { return new Hotline(canvas); } + + var defaultPalette = { + 0.0: 'green', + 0.5: 'yellow', + 1.0: 'red' + }; + + this._canvas = canvas = ('string' === typeof canvas) + ? document.getElementById(canvas) + : canvas; + + this._ctx = canvas.getContext('2d'); + this._width = canvas.width; + this._height = canvas.height; + + this._weight = 5; + this._outlineWidth = 1; + this._outlineColor = 'black'; + + this._min = 0; + this._max = 1; + + this._data = []; + + this.palette(defaultPalette); + }; + + Hotline.prototype = { + /** + * Sets the width of the canvas. Used when clearing the canvas. + * @param {number} width - Width of the canvas. + */ + width: function (width) { + this._width = width; + return this; + }, + + /** + * Sets the height of the canvas. Used when clearing the canvas. + * @param {number} height - Height of the canvas. + */ + height: function (height) { + this._height = height; + return this; + }, + + /** + * Sets the weight of the path. + * @param {number} weight - Weight of the path in px. + */ + weight: function (weight) { + this._weight = weight; + return this; + }, + + /** + * Sets the width of the outline around the path. + * @param {number} outlineWidth - Width of the outline in px. + */ + outlineWidth: function (outlineWidth) { + this._outlineWidth = outlineWidth; + return this; + }, + + /** + * Sets the color of the outline around the path. + * @param {string} outlineColor - A CSS color value. + */ + outlineColor: function (outlineColor) { + this._outlineColor = outlineColor; + return this; + }, + + /** + * Sets the palette gradient. + * @param {Object.<number, string>} palette - Gradient definition. + * e.g. { 0.0: 'white', 1.0: 'black' } + */ + palette: function (palette) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + gradient = ctx.createLinearGradient(0, 0, 0, 256); + + canvas.width = 1; + canvas.height = 256; + + for (var i in palette) { + gradient.addColorStop(i, palette[i]); + } + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 1, 256); + + this._palette = ctx.getImageData(0, 0, 1, 256).data; + + return this; + }, + + /** + * Sets the value used at the start of the palette gradient. + * @param {number} min + */ + min: function (min) { + this._min = min; + return this; + }, + + /** + * Sets the value used at the end of the palette gradient. + * @param {number} max + */ + max: function (max) { + this._max = max; + return this; + }, + + /** + * A path to rander as a hotline. + * @typedef Array.<{x:number, y:number, z:number}> Path - Array of x, y and z coordinates. + */ + + /** + * Sets the data that gets drawn on the canvas. + * @param {(Path|Path[])} data - A single path or an array of paths. + */ + data: function (data) { + this._data = data; + return this; + }, + + /** + * Adds a path to the list of paths. + * @param {Path} path + */ + add: function (path) { + this._data.push(path); + return this; + }, + + /** + * Draws the currently set paths. + */ + draw: function () { + var ctx = this._ctx; + + ctx.globalCompositeOperation = 'source-over'; + ctx.lineCap = 'round'; + + this._drawOutline(ctx); + this._drawHotline(ctx); + + return this; + }, + + /** + * Gets the RGB values of a given z value of the current palette. + * @param {number} value - Value to get the color for, should be between min and max. + * @returns {Array.<number>} The RGB values as an array [r, g, b] + */ + getRGBForValue: function (value) { + var valueRelative = Math.min(Math.max((value - this._min) / (this._max - this._min), 0), 0.999); + var paletteIndex = Math.floor(valueRelative * 256) * 4; + + return [ + this._palette[paletteIndex], + this._palette[paletteIndex + 1], + this._palette[paletteIndex + 2] + ]; + }, + + /** + * Draws the outline of the graphs. + * @private + */ + _drawOutline: function (ctx) { + var i, j, dataLength, path, pathLength, pointStart, pointEnd; + + if (this._outlineWidth) { + for (i = 0, dataLength = this._data.length; i < dataLength; i++) { + path = this._data[i]; + ctx.lineWidth = this._weight + 2 * this._outlineWidth; + + for (j = 1, pathLength = path.length; j < pathLength; j++) { + pointStart = path[j - 1]; + pointEnd = path[j]; + + ctx.strokeStyle = this._outlineColor; + ctx.beginPath(); + ctx.moveTo(pointStart.x, pointStart.y); + ctx.lineTo(pointEnd.x, pointEnd.y); + ctx.stroke(); + } + } + } + }, + + /** + * Draws the color encoded hotline of the graphs. + * @private + */ + _drawHotline: function (ctx) { + var i, j, dataLength, path, pathLength, pointStart, pointEnd, + gradient, gradientStartRGB, gradientEndRGB; + + ctx.lineWidth = this._weight; + + for (i = 0, dataLength = this._data.length; i < dataLength; i++) { + path = this._data[i]; + + for (j = 1, pathLength = path.length; j < pathLength; j++) { + pointStart = path[j - 1]; + pointEnd = path[j]; + + // Create a gradient for each segment, pick start end end colors from palette gradient + gradient = ctx.createLinearGradient(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y); + gradientStartRGB = this.getRGBForValue(pointStart.z); + gradientEndRGB = this.getRGBForValue(pointEnd.z); + gradient.addColorStop(0, 'rgb(' + gradientStartRGB.join(',') + ')'); + gradient.addColorStop(1, 'rgb(' + gradientEndRGB.join(',') + ')'); + + ctx.strokeStyle = gradient; + ctx.beginPath(); + ctx.moveTo(pointStart.x, pointStart.y); + ctx.lineTo(pointEnd.x, pointEnd.y); + ctx.stroke(); + } + } + } + }; + + + var Renderer = L.Canvas.extend({ + _initContainer: function () { + L.Canvas.prototype._initContainer.call(this); + this._hotline = new Hotline(this._container); + }, + + _update: function () { + L.Canvas.prototype._update.call(this); + this._hotline.width(this._container.width); + this._hotline.height(this._container.height); + }, + + _updatePoly: function (layer) { + if (!this._drawing) { return; } + + var parts = layer._parts; + + if (!parts.length) { return; } + + this._updateOptions(layer); + + this._hotline + .data(parts) + .draw(); + }, + + _updateOptions: function (layer) { + if (layer.options.min != null) { + this._hotline.min(layer.options.min); + } + if (layer.options.max != null) { + this._hotline.max(layer.options.max); + } + if (layer.options.weight != null) { + this._hotline.weight(layer.options.weight); + } + if (layer.options.outlineWidth != null) { + this._hotline.outlineWidth(layer.options.outlineWidth); + } + if (layer.options.outlineColor != null) { + this._hotline.outlineColor(layer.options.outlineColor); + } + if (layer.options.palette) { + this._hotline.palette(layer.options.palette); + } + } + }); + + var renderer = function (options) { + return L.Browser.canvas ? new Renderer(options) : null; + }; + + + var Util = { + /** + * This is just a copy of the original Leaflet version that support a third z coordinate. + * @see {@link http://leafletjs.com/reference.html#lineutil-clipsegment|Leaflet} + */ + clipSegment: function (a, b, bounds, useLastCode, round) { + var codeA = useLastCode ? this._lastCode : L.LineUtil._getBitCode(a, bounds), + codeB = L.LineUtil._getBitCode(b, bounds), + codeOut, p, newCode; + + // save 2nd code to avoid calculating it on the next segment + this._lastCode = codeB; + + while (true) { + // if a,b is inside the clip window (trivial accept) + if (!(codeA | codeB)) { + return [a, b]; + // if a,b is outside the clip window (trivial reject) + } else if (codeA & codeB) { + return false; + // other cases + } else { + codeOut = codeA || codeB; + p = L.LineUtil._getEdgeIntersection(a, b, codeOut, bounds, round); + newCode = L.LineUtil._getBitCode(p, bounds); + + if (codeOut === codeA) { + p.z = a.z; + a = p; + codeA = newCode; + } else { + p.z = b.z; + b = p; + codeB = newCode; + } + } + } + } + }; + + + L.Hotline = L.Polyline.extend({ + statics: { + Renderer: Renderer, + renderer: renderer + }, + + options: { + renderer: renderer(), + min: 0, + max: 1, + palette: { + 0.0: 'green', + 0.5: 'yellow', + 1.0: 'red' + }, + weight: 5, + outlineColor: 'black', + outlineWidth: 1 + }, + + getRGBForValue: function (value) { + return this._renderer._hotline.getRGBForValue(value); + }, + + /** + * Just like the Leaflet version, but with support for a z coordinate. + */ + _projectLatlngs: function (latlngs, result, projectedBounds) { + var flat = latlngs[0] instanceof L.LatLng, + len = latlngs.length, + i, ring; + + if (flat) { + ring = []; + for (i = 0; i < len; i++) { + ring[i] = this._map.latLngToLayerPoint(latlngs[i]); + // Add the altitude of the latLng as the z coordinate to the point + ring[i].z = latlngs[i].alt; + projectedBounds.extend(ring[i]); + } + result.push(ring); + } else { + for (i = 0; i < len; i++) { + this._projectLatlngs(latlngs[i], result, projectedBounds); + } + } + }, + + /** + * Just like the Leaflet version, but uses `Util.clipSegment()`. + */ + _clipPoints: function () { + if (this.options.noClip) { + this._parts = this._rings; + return; + } + + this._parts = []; + + var parts = this._parts, + bounds = this._renderer._bounds, + i, j, k, len, len2, segment, points; + + for (i = 0, k = 0, len = this._rings.length; i < len; i++) { + points = this._rings[i]; + + for (j = 0, len2 = points.length; j < len2 - 1; j++) { + segment = Util.clipSegment(points[j], points[j + 1], bounds, j, true); + + if (!segment) { continue; } + + parts[k] = parts[k] || []; + parts[k].push(segment[0]); + + // if segment goes out of screen, or it's the last one, it's the end of the line part + if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { + parts[k].push(segment[1]); + k++; + } + } + } + }, + + _clickTolerance: function () { + return this.options.weight / 2 + this.options.outlineWidth + (L.Browser.touch ? 10 : 0); + } + }); + + L.hotline = function (latlngs, options) { + return new L.Hotline(latlngs, options); + }; + + + return L; +}));
\ No newline at end of file |
