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/src/utils.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 'assets/static/vendor/leaflet-elevation/src/utils.js')
| -rw-r--r-- | assets/static/vendor/leaflet-elevation/src/utils.js | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/assets/static/vendor/leaflet-elevation/src/utils.js b/assets/static/vendor/leaflet-elevation/src/utils.js new file mode 100644 index 0000000..90035f7 --- /dev/null +++ b/assets/static/vendor/leaflet-elevation/src/utils.js @@ -0,0 +1,203 @@ +/** + * TODO: exget computed styles of theese values from actual "CSS vars" + **/ +export const Colors = { + 'lightblue': { area: '#3366CC', alpha: 0.45, stroke: '#3366CC' }, + 'magenta' : { area: '#FF005E' }, + 'yellow' : { area: '#FF0' }, + 'purple' : { area: '#732C7B' }, + 'steelblue': { area: '#4682B4' }, + 'red' : { area: '#F00' }, + 'lime' : { area: '#9CC222', line: '#566B13' } +}; + +const SEC = 1000; +const MIN = SEC * 60; +const HOUR = MIN * 60; +const DAY = HOUR * 24; + +export function resolveURL(src, baseUrl) { + return (new URL(src, (src.startsWith('../') || src.startsWith('./')) ? baseUrl : undefined)).toString() +}; + +/** + * Convert a time (millis) to a human readable duration string (%Dd %H:%M'%S") + */ +export function formatTime(t) { + let d = Math.floor(t / DAY); + let h = Math.floor( (t - d * DAY) / HOUR); + let m = Math.floor( (t - d * DAY - h * HOUR) / MIN); + let s = Math.round( (t - d * DAY - h * HOUR - m * MIN) / SEC); + if ( s === 60 ) { m++; s = 0; } + if ( m === 60 ) { h++; m = 0; } + if ( h === 24 ) { d++; h = 0; } + return (d ? d + "d " : '') + h.toString().padStart(2, 0) + ':' + m.toString().padStart(2, 0) + "'" + s.toString().padStart(2, 0) + '"'; +} + +/** + * Convert a time (millis) to human readable date string (dd-mm-yyyy hh:mm:ss) + */ + export function formatDate(format) { + if (!format) { + return (time) => (new Date(time)).toLocaleString().replaceAll('/', '-').replaceAll(',', ' '); + } else if (format == 'time') { + return (time) => (new Date(time)).toLocaleTimeString(); + } else if (format == 'date') { + return (time) => (new Date(time)).toLocaleDateString(); + } + return (time) => format(time); +} + +/** + * Generate download data event. + */ + export function saveFile(dataURI, fileName) { + let a = create('a', '', { href: dataURI, target: '_new', download: fileName || "", style: "display:none;" }); + let b = document.body; + b.appendChild(a); + a.click(); + b.removeChild(a); +} + + +/** + * Convert SVG Path into Path2D and then update canvas + */ + export function drawCanvas(ctx, path) { + path.classed('canvas-path', true); + + ctx.beginPath(); + ctx.moveTo(0, 0); + let p = new Path2D(path.attr('d')); + + ctx.strokeStyle = path.__strokeStyle || path.attr('stroke'); + ctx.fillStyle = path.__fillStyle || path.attr('fill'); + ctx.lineWidth = 1.25; + ctx.globalCompositeOperation = 'source-over'; + + // stroke opacity + ctx.globalAlpha = path.attr('stroke-opacity') || 0.3; + ctx.stroke(p); + + // fill opacity + ctx.globalAlpha = path.attr('fill-opacity') || 0.45; + ctx.fill(p); + + ctx.globalAlpha = 1; + + ctx.closePath(); +} + +/** + * Loop and extract GPX Extensions handled by "@tmcw/toGeoJSON" (eg. "coordinateProperties" > "times") + */ +export function coordPropsToMeta(coordProps, name, parser) { + return coordProps && (({props, point, id, isMulti }) => { + if (props) { + for (const key of coordProps) { + if (key in props) { + point.meta[name] = (parser || parseNumeric).call(this, (isMulti ? props[key][isMulti] : props[key]), id); + break; + } + } + } + }); +} + +/** + * Extract numeric property (id) from GeoJSON object + */ +export const parseNumeric = (property, id) => parseInt((typeof property === 'object' ? property[id] : property)); + +/** + * Extract datetime property (id) from GeoJSON object + */ +export const parseDate = (property, id) => new Date(Date.parse((typeof property === 'object' ? property[id] : property))); + +/** + * A little bit shorter than L.DomUtil + */ +export const addClass = (n, str) => n && str.split(" ").every(s => s && L.DomUtil.addClass(n, s)); +export const removeClass = (n, str) => n && str.split(" ").every(s => s && L.DomUtil.removeClass(n, s)); +export const toggleClass = (n, str, cond) => (cond ? addClass : removeClass)(n, str); +export const replaceClass = (n, rem, add) => (rem && removeClass(n, rem)) || (add && addClass(n, add)); +export const style = (n, k, v) => (typeof v === "undefined" && L.DomUtil.getStyle(n, k)) || n.style.setProperty(k, v); +export const toggleStyle = (n, k, v, cond) => style(n, k, cond ? v : ''); +export const setAttributes = (n, attrs) => { for (let k in attrs) { n.setAttribute(k, attrs[k]); } }; +export const toggleEvent = (el, e, fn, cond) => el[cond ? 'on' : 'off'](e, fn); +export const create = (tag, str, attrs, n) => { let elem = L.DomUtil.create(tag, str || ""); if (attrs) setAttributes(elem, attrs); if (n) append(n, elem); return elem; }; +export const append = (n, c) => n.appendChild(c); +export const insert = (n, c, pos) => n.insertAdjacentElement(pos, c); +export const select = (str, n) => (n || document).querySelector(str); +export const each = (obj, fn) => { for (let i in obj) fn(obj[i], i); }; +export const randomId = () => Math.random().toString(36).substr(2, 9); + +/** + * TODO: use generators instead? (ie. "yield") + */ +export const iMax = (iVal, max = -Infinity) => (iVal > max ? iVal : max); +export const iMin = (iVal, min = +Infinity) => (iVal < min ? iVal : min); +export const iAvg = (iVal, avg = 0, idx = 1) => (iVal + avg * (idx - 1)) / idx; +export const iSum = (iVal, sum = 0) => iVal + sum; + +/** + * Alias for some leaflet core functions + */ +export const { on, off } = L.DomEvent; +export const { throttle, wrapNum } = L.Util; +export const { hasClass } = L.DomUtil; + +/** + * Limit floating point precision + */ +export const round = L.Util.formatNum; + +/** + * Limit a number between min / max values + */ +export const clamp = (val, range) => range ? (val < range[0] ? range[0] : val > range[1] ? range[1] : val) : val; + +/** + * Limit a delta difference between two values + */ +export const wrapDelta = (curr, prev, deltaMax) => Math.abs(curr - prev) > deltaMax ? prev + deltaMax * Math.sign(curr - prev) : curr; + +/** + * A deep copy implementation that takes care of correct prototype chain and cycles, references + * + * @see https://web.dev/structured-clone/#features-and-limitations + */ +export function cloneDeep(o, skipProps = [], cache = []) { + switch(!o || typeof o) { + case 'object': + const hit = cache.filter(c => o === c.original)[0]; + if (hit) return hit.copy; // handle circular structures + const copy = Array.isArray(o) ? [] : Object.create(Object.getPrototypeOf(o)); + cache.push({ original: o, copy }); + Object + .getOwnPropertyNames(o) + .forEach(function (prop) { + const propdesc = Object.getOwnPropertyDescriptor(o, prop); + Object.defineProperty( + copy, + prop, + propdesc.get || propdesc.set + ? propdesc // just copy accessor properties + : { // deep copy data properties + writable: propdesc.writable, + configurable: propdesc.configurable, + enumerable: propdesc.enumerable, + value: skipProps.includes(prop) ? propdesc.value : cloneDeep(propdesc.value, skipProps, cache), + } + ); + }); + return copy; + case 'function': + case 'symbol': + console.warn('cloneDeep: ' + typeof o + 's not fully supported:', o); + case true: + // null, undefined or falsy primitive + default: + return o; + } +}
\ No newline at end of file |
