@goshawk22/leaflet-elevation
Version:
A Leaflet plugin that allows to add elevation profiles using d3js
203 lines (180 loc) • 7.27 kB
JavaScript
/**
* 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;
}
}