ol-elevation-profile 0.5.0 → 0.6.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 CHANGED
@@ -68,13 +68,15 @@ vectorSource.addFeatures(feats)
68
68
  profile.setFeature(feats[0]) // or let a click on the track select it
69
69
  ```
70
70
 
71
+ Any OpenLayers-readable format works (GeoJSON, GPX, KML, …) — the control only consumes OL `Feature`s. Read GPX/KML with `ol.format.GPX` / `ol.format.KML` and pass the line feature to `setFeature`.
72
+
71
73
  Full option reference: see the [documentation](#documentation).
72
74
 
73
75
  ## Features
74
76
 
75
77
  ### Slope classes
76
78
 
77
- With `slope: true`, the profile is split into contiguous portions of the same slope class (width `slopeClassSize`, in %), capped at `maxClasses` (default 8). Colours run from **blue** (flattest class) to **red** (steepest), via cyan/green and a pure yellow, spread across the classes that are actually present not stretched on the real maximum slope. A vertical separator marks each class change, and a legend appears under the title.
79
+ With `slope: true`, the profile is split into contiguous portions of the same slope class (width `slopeClassSize`, in %), capped at `maxClasses` (default 8). Colours run from **blue** (flattest class) to **red** (steepest), via cyan/green and a pure yellow, spread across the classes that are actually present (not stretched on the real maximum slope). A vertical separator marks each class change, and a legend appears under the title.
78
80
 
79
81
  ### Smoothing
80
82
 
@@ -84,13 +86,17 @@ With `slope: true`, the profile is split into contiguous portions of the same sl
84
86
 
85
87
  When the profile occupies the bottom-right corner (or full-width at the bottom), the OpenLayers attribution control is automatically lifted **above** the profile, right-aligned, with a vertical gap equal to the map-edge-to-profile-bottom gap. Other placements leave the attribution untouched.
86
88
 
89
+ ### Time
90
+
91
+ If the track carries time data (`coordTimes` ISO timestamps from GPX `<time>`, `coordinateProperties.times`, or a 4th `M` coordinate), add `'duration'` to `headerItems` for the **total elapsed time** in the title, and `'time'` to `tooltipItems` for the **elapsed time at the cursor**. The unit adapts: `7 sec`, `26 min`, `1 h 48 min`, `2 j 3 h`. By default this is **moving time** (stopped segments below `stopSpeed`, 0.5 m/s, are excluded); set `ignoreStops: false` for raw wall-clock time. Detect availability with `OlElevationProfile.featureHasTime(feature)`.
92
+
87
93
  ## Demo
88
94
 
89
- Live, interactive demo (toggle every option): **https://lc-4918.github.io/ol-elevation-profile/demo/**
95
+ Live, interactive [demo](https://lc-4918.github.io/ol-elevation-profile/demo/) (toggle every option)
90
96
 
91
97
  ## Documentation
92
98
 
93
- Full guide and API (English & French): **https://lc-4918.github.io/ol-elevation-profile/**
99
+ [Full guide and API](https://lc-4918.github.io/ol-elevation-profile/) (English & French)
94
100
 
95
101
  ## License
96
102
 
@@ -108,3 +108,5 @@
108
108
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35); pointer-events: none;
109
109
  }
110
110
  .ol-elevation-profile.oep-floating { transform: none; }
111
+
112
+ .oep-time{opacity:.85}
@@ -55,7 +55,6 @@ import * as d3 from 'd3';
55
55
  slopeSeparators: true, // ligne verticale à chaque changement de classe
56
56
  slopeLegend: true,
57
57
  maxClasses: 8, // nombre maximal de classes de pente (couleurs + légende)
58
- maxLegendClasses: 8,
59
58
  xTicks: null,
60
59
  yTicks: null,
61
60
  show: 'click',
@@ -67,6 +66,8 @@ import * as d3 from 'd3';
67
66
  responsive: true, // adapte la largeur/placement, mobile inclus
68
67
  mobileBreakpoint: 640, // <= largeur écran -> mode mobile (100% largeur, top/bottom)
69
68
  zoom: false, // boutons début/fin pour recadrer carte + profil sur A..B
69
+ ignoreStops: true, // durée = temps en mouvement (ignore les arrêts)
70
+ stopSpeed: 0.5, // seuil d'arrêt en m/s (~1,8 km/h)
70
71
  tooltipItems: ['distance', 'elevation'],
71
72
  headerItems: ['distance', 'ascent', 'descent', 'minmax'],
72
73
  titleProperty: 'name',
@@ -74,6 +75,8 @@ import * as d3 from 'd3';
74
75
  labels: {
75
76
  distance: 'Distance', elevation: 'Altitude', slope: 'Pente',
76
77
  ascent: 'D+', descent: 'D-', empty: 'Cliquez un tracé',
78
+ time: 'Temps', duration: 'Durée',
79
+ durationUnits: { s: 'sec', m: 'min', h: 'h', d: 'j' },
77
80
  zoomStart: 'Définir le début (A)', zoomEnd: 'Définir la fin (B)', zoomAll: 'Tout voir'
78
81
  }
79
82
  };
@@ -91,6 +94,30 @@ import * as d3 from 'd3';
91
94
  const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
92
95
  const isUrl = (v) => typeof v === 'string' && /^(https?:)?\/\/|^mailto:/i.test(v);
93
96
 
97
+ // Extrait un tableau plat de timestamps (ms epoch) aligné sur l'ordre plat des coordonnées,
98
+ // depuis properties.coordTimes (ISO ou nombre), coordinateProperties.times, ou la 4e dimension (M).
99
+ function extractTimes(feature, lines) {
100
+ const props = (feature && feature.getProperties) ? feature.getProperties() : {};
101
+ let raw = props.coordTimes;
102
+ if (raw == null && props.coordinateProperties) raw = props.coordinateProperties.times || props.coordinateProperties.coordTimes;
103
+ let flat = null;
104
+ if (Array.isArray(raw)) flat = Array.isArray(raw[0]) ? raw.reduce((a, b) => a.concat(b), []) : raw.slice();
105
+ if (!flat && lines) { // repli : 4e dimension M (layout XYZM)
106
+ const tmp = []; let any = false;
107
+ for (const seg of lines) for (const c of seg) { const v = c.length > 3 ? c[3] : null; tmp.push(v); if (v != null && isFinite(v)) any = true; }
108
+ flat = any ? tmp : null;
109
+ }
110
+ if (!flat) return null;
111
+ let valid = 0;
112
+ const ms = flat.map((v) => {
113
+ if (v == null || v === '') return null;
114
+ const t = (typeof v === 'number') ? (v > 1e12 ? v : v * 1000) : Date.parse(v); // epoch ms / epoch s / ISO
115
+ if (!isFinite(t)) return null;
116
+ valid++; return t;
117
+ });
118
+ return valid >= 2 ? ms : null;
119
+ }
120
+
94
121
  function colorToCss(c) {
95
122
  if (c == null) return null;
96
123
  if (typeof c === 'string') return c;
@@ -161,8 +188,10 @@ import * as d3 from 'd3';
161
188
  * @property {boolean} [responsive=true] Adapt width/placement; mobile included.
162
189
  * @property {number} [mobileBreakpoint=640] Below this width: mobile mode (100% width, top/bottom only).
163
190
  * @property {boolean} [zoom=false] A/B buttons to crop map + profile to a sub-range.
164
- * @property {Array<'distance'|'elevation'|'slope'>} [tooltipItems=['distance','elevation']] Tooltip content.
165
- * @property {Array<string|{property:string,label?:string,asLink?:boolean,linkText?:string}>} [headerItems] Header content.
191
+ * @property {boolean} [ignoreStops=true] When computing time, ignore stopped segments (moving time).
192
+ * @property {number} [stopSpeed=0.5] Speed threshold (m/s) below which a segment counts as a stop.
193
+ * @property {Array<'distance'|'elevation'|'slope'|'time'>} [tooltipItems=['distance','elevation']] Tooltip content (`'time'` = elapsed time at the cursor, if the track has time data).
194
+ * @property {Array<string|{property:string,label?:string,asLink?:boolean,linkText?:string}>} [headerItems] Header content (string tokens: distance, ascent, descent, min, max, minmax, `'duration'` = total elapsed time).
166
195
  * @property {string} [titleProperty='name'] Feature property used as the title.
167
196
  * @property {?string} [titleLink=null] Feature property holding a URL → clickable title.
168
197
  * @property {number} [maxPoints=2000] Decimation for render/interaction (stats use full data).
@@ -213,6 +242,18 @@ import * as d3 from 'd3';
213
242
  return false;
214
243
  }
215
244
 
245
+ /**
246
+ * Whether a feature carries per-point time data (coordTimes / XYZM).
247
+ * @param {import('ol/Feature').default} feature
248
+ * @returns {boolean}
249
+ */
250
+ static featureHasTime(feature) {
251
+ const g = feature && feature.getGeometry && feature.getGeometry();
252
+ if (!g) return false;
253
+ const lines = g.getType() === 'MultiLineString' ? g.getCoordinates() : [g.getCoordinates()];
254
+ return !!extractTimes(feature, lines);
255
+ }
256
+
216
257
  // ---------- DOM ---------------------------------------------------
217
258
  _buildDom(root) {
218
259
  const o = this.options;
@@ -428,13 +469,28 @@ import * as d3 from 'd3';
428
469
  const lines = geom.getType() === 'MultiLineString' ? geom.getCoordinates() : [geom.getCoordinates()];
429
470
  const dataProj = o.dataProjection || (this.getMap() && this.getMap().getView().getProjection()) || 'EPSG:3857';
430
471
 
472
+ const times = extractTimes(this._feature, lines);
473
+ this._hasTime = !!times;
474
+ const ignoreStops = o.ignoreStops !== false; // défaut : ignore les arrêts
475
+ const stopSpeed = (o.stopSpeed != null ? o.stopSpeed : 0.5); // m/s
431
476
  const pts = [];
432
- let cum = 0, prev = null;
477
+ let cum = 0, prev = null, i = -1, tAcc = 0, prevMs = null;
433
478
  for (const seg of lines) for (const c of seg) {
479
+ i++;
434
480
  const ll = toLonLat(c, dataProj);
435
- if (prev) cum += getDistance(prev, ll);
481
+ let dseg = 0;
482
+ if (prev) { dseg = getDistance(prev, ll); cum += dseg; }
483
+ let t = null;
484
+ if (times && times[i] != null) {
485
+ const ms = times[i];
486
+ if (prevMs != null) {
487
+ const dt = (ms - prevMs) / 1000; // s sur le segment
488
+ if (dt > 0 && (!ignoreStops || (dseg / dt) >= stopSpeed)) tAcc += dt;
489
+ }
490
+ t = tAcc; prevMs = ms;
491
+ }
436
492
  prev = ll;
437
- pts.push({ x: cum, z: (c.length > 2 && isFinite(c[2])) ? c[2] : 0, coord: c });
493
+ pts.push({ x: cum, z: (c.length > 2 && isFinite(c[2])) ? c[2] : 0, coord: c, t });
438
494
  }
439
495
  if (o.smoothing > 0) this._smooth(pts, o.smoothing);
440
496
 
@@ -452,7 +508,9 @@ import * as d3 from 'd3';
452
508
  const s = Math.abs(samples[k].slope || 0); if (s > maxAbs) maxAbs = s;
453
509
  }
454
510
  const distance = samples.length ? samples[samples.length - 1].x - samples[0].x : 0;
455
- return { distance, ascent, descent, min: isFinite(zmin) ? zmin : 0, max: isFinite(zmax) ? zmax : 0, maxAbsSlope: maxAbs, points: samples.length };
511
+ const ta = samples.length ? samples[0].t : null, tb = samples.length ? samples[samples.length - 1].t : null;
512
+ const duration = (ta != null && tb != null) ? (tb - ta) : null;
513
+ return { distance, duration, ascent, descent, min: isFinite(zmin) ? zmin : 0, max: isFinite(zmax) ? zmax : 0, maxAbsSlope: maxAbs, points: samples.length };
456
514
  }
457
515
  _smooth(pts, meters) {
458
516
  if (!(meters > 0) || pts.length < 3) return;
@@ -467,7 +525,7 @@ import * as d3 from 'd3';
467
525
  }
468
526
  }
469
527
  _decimate(pts, maxPoints) {
470
- const copy = (p) => ({ x: p.x, z: p.z, coord: p.coord });
528
+ const copy = (p) => ({ x: p.x, z: p.z, coord: p.coord, t: p.t });
471
529
  if (!maxPoints || pts.length <= maxPoints) return pts.map(copy);
472
530
  const step = pts.length / maxPoints, out = [];
473
531
  for (let i = 0; i < maxPoints; i++) out.push(copy(pts[Math.floor(i * step)]));
@@ -506,6 +564,24 @@ import * as d3 from 'd3';
506
564
  }
507
565
  _classIndex(slope, sc) { return Math.min(sc.maxIdx, Math.floor(Math.abs(slope) / sc.classSize)); }
508
566
 
567
+ // ---------- temps : format adaptatif ------------------------------
568
+ // 7 sec · 26 min · 1 h 48 min · 2 j 3 h (jours + heures normalisés)
569
+ _fmtDuration(sec) {
570
+ if (sec == null || !isFinite(sec)) return '';
571
+ const u = (this.options.labels && this.options.labels.durationUnits) || { s: 'sec', m: 'min', h: 'h', d: 'j' };
572
+ sec = Math.max(0, Math.round(sec));
573
+ if (sec < 60) return sec + ' ' + u.s;
574
+ if (sec < 3600) return Math.round(sec / 60) + ' ' + u.m;
575
+ if (sec < 86400) {
576
+ let h = Math.floor(sec / 3600), m = Math.round((sec % 3600) / 60);
577
+ if (m === 60) { h++; m = 0; }
578
+ return m ? `${h} ${u.h} ${m} ${u.m}` : `${h} ${u.h}`;
579
+ }
580
+ let d = Math.floor(sec / 86400), h = Math.round((sec % 86400) / 3600);
581
+ if (h === 24) { d++; h = 0; }
582
+ return h ? `${d} ${u.d} ${h} ${u.h}` : `${d} ${u.d}`;
583
+ }
584
+
509
585
  // ---------- entête + légende --------------------------------------
510
586
  _renderHeader() {
511
587
  const o = this.options, s = this._stats, f = this._feature;
@@ -524,6 +600,7 @@ import * as d3 from 'd3';
524
600
  else if (it === 'min') { html.push(fmtElevation(s.min, o.units)); text.push(fmtElevation(s.min, o.units)); }
525
601
  else if (it === 'max') { html.push(fmtElevation(s.max, o.units)); text.push(fmtElevation(s.max, o.units)); }
526
602
  else if (it === 'minmax') { const v = `${fmtElevation(s.min, o.units)}–${fmtElevation(s.max, o.units)}`; html.push(v); text.push(v); }
603
+ else if (it === 'duration') { if (s.duration != null) { const v = this._fmtDuration(s.duration); html.push(`<span class="oep-time">${esc(o.labels.duration)} ${v}</span>`); text.push(`${o.labels.duration} ${v}`); } }
527
604
  } else if (it && it.property) {
528
605
  const val = f.get && f.get(it.property); if (val == null || val === '') return;
529
606
  const lbl = it.label ? `${it.label} ` : '';
@@ -664,7 +741,8 @@ import * as d3 from 'd3';
664
741
  const raw = this._fullSamples.filter((p) => p.x >= a && p.x <= b);
665
742
  if (raw.length < 2) return;
666
743
  const off = raw[0].x; // A devient 0
667
- const cropped = raw.map((p) => ({ x: p.x - off, z: p.z, coord: p.coord, slope: p.slope }));
744
+ const tOff = raw[0].t != null ? raw[0].t : 0;
745
+ const cropped = raw.map((p) => ({ x: p.x - off, z: p.z, coord: p.coord, slope: p.slope, t: (p.t != null ? p.t - tOff : null) }));
668
746
  const coords = raw.map((p) => p.coord);
669
747
  this._samples = cropped; this._stats = this._statsOf(cropped, false); this._cropMode = true;
670
748
  this._updateZoomButtons(); this._render();
@@ -691,6 +769,7 @@ import * as d3 from 'd3';
691
769
  if (it === 'distance') parts.push(fmtDistance(d.x, o.units));
692
770
  else if (it === 'elevation') parts.push(fmtElevation(d.z, o.units));
693
771
  else if (it === 'slope') parts.push(fmtSlope(d.slope || 0));
772
+ else if (it === 'time') { if (d.t != null) parts.push(this._fmtDuration(d.t)); }
694
773
  });
695
774
  return parts.join(' · ');
696
775
  }
@@ -735,6 +814,6 @@ import * as d3 from 'd3';
735
814
  ElevationProfile.addTheme = (name, colors) => { THEMES[name] = colors; };
736
815
  ElevationProfile.THEMES = THEMES;
737
816
  ElevationProfile.POSITIONS = POSITIONS;
738
- ElevationProfile.version = '0.5.0';
817
+ ElevationProfile.version = '0.6.0';
739
818
 
740
819
  export { ElevationProfile as default };
@@ -1,5 +1,5 @@
1
1
  /*! ol-elevation-profile | MIT License | https://github.com/lc-4918/ol-elevation-profile */
2
- import t from"ol/control/Control.js";import e from"ol/Overlay.js";import{unByKey as s}from"ol/Observable.js";import{toLonLat as o}from"ol/proj.js";import{getDistance as i}from"ol/sphere.js";import{boundingExtent as l}from"ol/extent.js";import*as n from"d3";
2
+ import t from"ol/control/Control.js";import e from"ol/Overlay.js";import{unByKey as s}from"ol/Observable.js";import{toLonLat as o}from"ol/proj.js";import{getDistance as i}from"ol/sphere.js";import{boundingExtent as n}from"ol/extent.js";import*as l from"d3";
3
3
  /**
4
4
  * Synchronized elevation profile control for OpenLayers, rendered with d3.
5
5
  *
@@ -13,4 +13,4 @@ import t from"ol/control/Control.js";import e from"ol/Overlay.js";import{unByKey
13
13
  *
14
14
  * @module ol-elevation-profile
15
15
  * @license MIT
16
- */const a={steelblue:{area:"#4682b4",line:"#3a6d96",axis:"#555",text:"#222",focus:"#e6550d"},lime:{area:"#9ccc2f",line:"#7da521",axis:"#555",text:"#222",focus:"#d62728"},purple:{area:"#9467bd",line:"#76529c",axis:"#555",text:"#222",focus:"#ff9f1c"},slate:{area:"#7c8a99",line:"#4a5560",axis:"#5a6570",text:"#26303a",focus:"#2f81f7"},graphite:{area:"#9a948c",line:"#5c574f",axis:"#5c574f",text:"#2b2823",focus:"#e07a3f"},amber:{area:"#f0a23b",line:"#c4671a",axis:"#7a5a36",text:"#3a2a16",focus:"#1f6fb2"}},r=["top","bottom","left","right","top-left","top-right","bottom-left","bottom-right"],h={immersion:"docked",position:"bottom",width:520,height:180,margins:{unit:"px",top:20,right:24,bottom:30,left:48},units:"meters",dataProjection:null,maxPoints:2e3,smoothing:0,theme:"steelblue",color:null,trackLayer:null,transparency:!1,transparencyLevel:.45,grid:!0,slope:!1,slopeClassSize:2.5,slopeColors:null,slopeSeparators:!0,slopeLegend:!0,maxClasses:8,maxLegendClasses:8,xTicks:null,yTicks:null,show:"click",collapsable:!0,collapsed:!1,followMap:!0,marker:!0,hideOnMapClick:!0,responsive:!0,mobileBreakpoint:640,zoom:!1,tooltipItems:["distance","elevation"],headerItems:["distance","ascent","descent","minmax"],titleProperty:"name",titleLink:null,labels:{distance:"Distance",elevation:"Altitude",slope:"Pente",ascent:"D+",descent:"D-",empty:"Cliquez un tracé",zoomStart:"Définir le début (A)",zoomEnd:"Définir la fin (B)",zoomAll:"Tout voir"}};function p(t,e){const s={};return Object.keys(t).forEach(e=>{s[e]=t[e]}),e&&Object.keys(e).forEach(o=>{e[o]&&"object"==typeof e[o]&&!Array.isArray(e[o])&&t[o]&&"object"==typeof t[o]?s[o]=p(t[o],e[o]):s[o]=e[o]}),s}const c=t=>String(t).replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[t])),d=t=>"string"==typeof t&&/^(https?:)?\/\/|^mailto:/i.test(t);function u(t,e,s){let o=t;if("function"==typeof o)try{o=o(e,s)}catch(t){return null}if(Array.isArray(o)&&(o=o[0]),o&&o.getStroke){const t=o.getStroke();if(t)return t.getColor()}return null}const m=(t,e)=>{if("imperial"===e){const e=t/1609.344;return e<.2?`${Math.round(3.28084*t)} ft`:`${e.toFixed(e<10?2:1)} mi`}return t<1e3?`${Math.round(t)} m`:`${(t/1e3).toFixed(t<1e4?2:1)} km`},_=(t,e)=>"imperial"===e?`${Math.round(3.28084*t)} ft`:`${Math.round(t)} m`;const f='<svg viewBox="0 0 24 24"><path d="M5 13h14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>',g='<svg viewBox="0 0 24 24"><path d="M3 19l5-7 4 4 4-6 5 9z" fill="currentColor" opacity=".85"/></svg>';class y extends t{constructor(t){const e=p(h,t||{});-1===r.indexOf(e.position)&&(e.position="bottom");const s=function(t){const e=document.createElement("div");return e.className=`ol-elevation-profile ol-unselectable ol-control oep-pos-${t.position} oep-theme-${"string"==typeof t.theme?t.theme:"custom"}`+("floating"===t.immersion?" oep-floating":""),e}(e);super({element:s,target:t&&t.target}),this.options=e,this.slopeColors=e.slopeColors||null,this._feature=null,this._fullSamples=null,this._fullStats=null,this._samples=null,this._stats=null,this._marker=null,this._collapsed=!!e.collapsed,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._onResize=()=>{this._feature&&!this._collapsed&&this._render()},this._buildDom(s),s.style.display="none"}static featureHasZ(t){const e=t&&t.getGeometry&&t.getGeometry();if(!e)return!1;const s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()];for(const t of s)for(const e of t)if(e.length>2&&isFinite(e[2]))return!0;return!1}_buildDom(t){const e=this.options;this._applyTheme(),this._applyTransparency();const s=document.createElement("div");s.className="oep-header",this._titleEl=document.createElement("span"),this._titleEl.className="oep-title",this._titleEl.textContent=e.labels.empty,this._statsEl=document.createElement("span"),this._statsEl.className="oep-stats",s.appendChild(this._titleEl),s.appendChild(this._statsEl),this._toolbar=document.createElement("span"),this._toolbar.className="oep-toolbar";const o=(t,e,s,o)=>{const i=document.createElement("button");return i.type="button",i.className=`oep-tbtn ${t}`,i.innerHTML=e,i.title=s,i.setAttribute("aria-label",s),i.addEventListener("click",o),this._toolbar.appendChild(i),i};this._btnA=o("oep-a",'<svg viewBox="0 0 24 24"><path d="M7 5v14M11 12h8m0 0-3-3m3 3-3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomStart,()=>this._arm("A")),this._btnB=o("oep-b",'<svg viewBox="0 0 24 24"><path d="M17 5v14M13 12H5m0 0 3-3m-3 3 3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomEnd,()=>this._arm("B")),this._btnAll=o("oep-all",'<svg viewBox="0 0 24 24"><path d="M4 12h16M4 12l4-4M4 12l4 4M20 12l-4-4M20 12l-4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomAll,()=>this._exitZoom()),s.appendChild(this._toolbar);const i=document.createElement("button");i.className="oep-toggle",i.type="button",i.setAttribute("aria-label","Réduire ou agrandir le profil"),i.innerHTML=this._collapsed?g:f,i.addEventListener("click",()=>this.toggleCollapsed()),s.appendChild(i),this._toggleBtn=i,this._legendEl=document.createElement("div"),this._legendEl.className="oep-legend",this._legendEl.style.display="none",this._body=document.createElement("div"),this._body.className="oep-body",t.appendChild(s),t.appendChild(this._legendEl),t.appendChild(this._body),this._applyCollapsable(),this._updateZoomButtons(),this._collapsed&&t.classList.add("oep-collapsed")}_resolveColor(){const t=this.options;return t.color?"auto"===t.color?this._featureColor():t.color:null}_featureColor(){const t=this._feature;if(!t)return null;const e=this.getMap()?this.getMap().getView().getResolution():1;let s=u(t.getStyle&&t.getStyle(),t,e);return!s&&this.options.trackLayer&&(s=u(this.options.trackLayer.getStyle&&this.options.trackLayer.getStyle(),t,e)),function(t){if(null==t)return null;if("string"==typeof t)return t;if(Array.isArray(t)){const e=t.length>3?t[3]:1;return`rgba(${0|t[0]},${0|t[1]},${0|t[2]},${e})`}return null}(s)}_applyTheme(){const t=this.options,e="object"==typeof t.theme?t.theme:a[t.theme]||a.steelblue;this.themeColors=e;const s=this.element,o=this._resolveColor(),i=o||e.area;let l=e.line;if(o)try{l=String(n.color(o).darker(.7))}catch(t){l=o}s.style.setProperty("--oep-area",i),s.style.setProperty("--oep-line",l),s.style.setProperty("--oep-axis",e.axis),s.style.setProperty("--oep-text",e.text),s.style.setProperty("--oep-focus",e.focus)}_applyTransparency(){const t=this.options.transparency;let e;e=!1===t||null==t?1:!0===t?this.options.transparencyLevel:Math.max(0,Math.min(1,+t)),this.element.style.setProperty("--oep-bg",`rgba(255,255,255,${e})`),this.element.classList.toggle("oep-transparent",e<1)}_applyCollapsable(){const t=this.options.collapsable;this._toggleBtn.style.display=t?"":"none",!t&&this._collapsed&&this.toggleCollapsed(!1)}toggleCollapsed(t){this._collapsed="boolean"==typeof t?t:!this._collapsed,this.element.classList.toggle("oep-collapsed",this._collapsed),this._toggleBtn.innerHTML=this._collapsed?g:f,!this._collapsed&&this._feature&&this._render(),this._adjustAttribution()}_availWidth(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement();return e&&e.clientWidth||("undefined"!=typeof window?window.innerWidth:1024)}_isMobile(){return this.options.responsive&&"undefined"!=typeof window&&window.innerWidth<=(this.options.mobileBreakpoint||640)}_applyPlacement(){const t=this._isMobile();let e=this.options.position;return t&&(e=/top/.test(e)?"top":"bottom"),this.element.className=this.element.className.replace(/oep-pos-\S+/,`oep-pos-${e}`),this.element.classList.toggle("oep-mobile",t),t}_adjustAttribution(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement(),s=e&&e.querySelector&&e.querySelector(".ol-attribution");if(!s)return;if(s.style.bottom="",s.style.right="","none"===this.element.style.display||this._collapsed)return;const o=e.getBoundingClientRect(),i=this.element.getBoundingClientRect();if(!i.height)return;const l=o.bottom-i.bottom,n=o.right-i.right;l<i.height&&n<24&&(s.style.right=`${Math.max(0,Math.round(n))}px`,s.style.bottom=`${Math.round(i.height+2*l)}px`)}setMap(t){const o=this.getMap();if(super.setMap(t),this._mapKeys&&(this._mapKeys.forEach(s),this._mapKeys=null),o&&"undefined"!=typeof window&&window.removeEventListener("resize",this._onResize),this._marker&&!t&&this._marker.setPosition(void 0),!t)return;const i=this.options;if(i.dataProjection||(i.dataProjection=t.getView().getProjection()),i.marker&&!this._marker){const s=document.createElement("div");s.className="oep-marker",this._marker=new e({element:s,positioning:"center-center",stopEvent:!1}),t.addOverlay(this._marker)}"undefined"!=typeof window&&window.addEventListener("resize",this._onResize);const l=e=>t.forEachFeatureAtPixel(e,t=>{const e=t.getGeometry();return e&&/LineString/.test(e.getType())?t:void 0});this._mapKeys=[],this._mapKeys.push(t.on("mouseover"===i.show?"pointermove":"click",t=>{const e=l(t.pixel);e&&e!==this._feature&&this.setFeature(e)})),i.hideOnMapClick&&this._mapKeys.push(t.on("click",t=>{!l(t.pixel)&&this._feature&&this.clear()})),i.followMap&&this._mapKeys.push(t.on("pointermove",e=>{if(!this._feature||this._collapsed)return;const s=this._feature.getGeometry().getClosestPoint(e.coordinate),o=t.getPixelFromCoordinate(s);o&&(Math.hypot(o[0]-e.pixel[0],o[1]-e.pixel[1])<14?this._focusByCoord(s):this._clearFocus())})),this._mapKeys.push(t.on("moveend",()=>{this._cropMode&&this._fitRes&&t.getView().getResolution()>1.25*this._fitRes&&this._exitZoom()})),this._mapKeys.push(t.on("change:size",this._onResize))}setFeature(t){return this._feature=t||null,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,t?(this.element.style.display="",this._compute(),this._updateZoomButtons(),this._collapsed||this._render(),this):(this._fullSamples=this._samples=null,this._clear(),this)}clear(){return this.setFeature(null)}getStats(){return this._stats}setTheme(t){return this.options.theme=t,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setColor(t){return this.options.color=t||null,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setOptions(t){return this.options=p(this.options,t||{}),t&&"slopeColors"in t&&(this.slopeColors=t.slopeColors||null),t&&(t.theme||"color"in t)&&this._applyTheme(),t&&("transparency"in t||"transparencyLevel"in t)&&this._applyTransparency(),t&&"collapsable"in t&&this._applyCollapsable(),t&&"zoom"in t&&this._updateZoomButtons(),t&&void 0!==t.width&&"number"==typeof t.width&&(this.options.width=t.width),this._feature&&(this._compute(),this._updateZoomButtons(),this._collapsed||this._render()),this}_compute(){const t=this.options,e=this._feature.getGeometry(),s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()],l=t.dataProjection||this.getMap()&&this.getMap().getView().getProjection()||"EPSG:3857",n=[];let a=0,r=null;for(const t of s)for(const e of t){const t=o(e,l);r&&(a+=i(r,t)),r=t,n.push({x:a,z:e.length>2&&isFinite(e[2])?e[2]:0,coord:e})}t.smoothing>0&&this._smooth(n,t.smoothing);const h=this._decimate(n,t.maxPoints);this._addSlope(h),this._fullSamples=h,this._fullStats=this._statsOf(h,!0),this._samples=h,this._stats=this._fullStats}_statsOf(t,e){let s=0,o=0,i=1/0,l=-1/0,n=0;for(let e=0;e<t.length;e++){const a=t[e].z;if(a<i&&(i=a),a>l&&(l=a),e>0){const i=a-t[e-1].z;i>0?s+=i:o-=i}const r=Math.abs(t[e].slope||0);r>n&&(n=r)}return{distance:t.length?t[t.length-1].x-t[0].x:0,ascent:s,descent:o,min:isFinite(i)?i:0,max:isFinite(l)?l:0,maxAbsSlope:n,points:t.length}}_smooth(t,e){if(!(e>0)||t.length<3)return;const s=e/2,o=t.map(t=>t.z);let i=0,l=0,n=0;for(let e=0;e<t.length;e++){const a=t[e].x;for(;i<t.length&&t[i].x<a-s;)n-=o[i],i++;for(;l<t.length&&t[l].x<=a+s;)n+=o[l],l++;t[e].z=l>i?n/(l-i):o[e]}}_decimate(t,e){const s=t=>({x:t.x,z:t.z,coord:t.coord});if(!e||t.length<=e)return t.map(s);const o=t.length/e,i=[];for(let l=0;l<e;l++)i.push(s(t[Math.floor(l*o)]));return i.push(s(t[t.length-1])),i}_addSlope(t){for(let e=1;e<t.length;e++){const s=t[e].x-t[e-1].x;t[e].slope=s>0?(t[e].z-t[e-1].z)/s*100:0}t.length&&(t[0].slope=t.length>1?t[1].slope:0)}_slopeScale(){const t=this.options.slopeClassSize||2.5,e=this.options.maxClasses||8,s=Math.max(1,Math.floor((this._stats.maxAbsSlope||0)/t)),o=Math.min(s,e-1),i=s>o;let l;if(this.slopeColors&&this.slopeColors.length){const t=n.interpolateRgbBasis(this.slopeColors);l=e=>t(o?e/o:0)}else{const t=n.scaleLinear().domain([0,.16,.42,.68,1]).range(["#2166ac","#27a35a","#ffe000","#f4791f","#d7191c"]).interpolate(n.interpolateRgb).clamp(!0);l=e=>String(t(o?e/o:0))}return{classSize:t,maxIdx:o,capped:i,colorByIndex:l}}_classIndex(t,e){return Math.min(e.maxIdx,Math.floor(Math.abs(t)/e.classSize))}_renderHeader(){const t=this.options,e=this._stats,s=this._feature,o=s.get&&s.get(t.titleProperty)||"Profil",i=t.titleLink&&s.get&&s.get(t.titleLink);d(i)?this._titleEl.innerHTML=`<a href="${c(i)}" target="_blank" rel="noopener">${c(o)}</a>`:this._titleEl.textContent=o,this._titleEl.setAttribute("title",o);const l=[],n=[];if(t.headerItems.forEach(o=>{if("string"==typeof o){if("distance"===o)l.push(`<b>${m(e.distance,t.units)}</b>`),n.push(m(e.distance,t.units));else if("ascent"===o)l.push(`<span class="oep-up">${t.labels.ascent} ${_(e.ascent,t.units)}</span>`),n.push(`${t.labels.ascent} ${_(e.ascent,t.units)}`);else if("descent"===o)l.push(`<span class="oep-down">${t.labels.descent} ${_(e.descent,t.units)}</span>`),n.push(`${t.labels.descent} ${_(e.descent,t.units)}`);else if("min"===o)l.push(_(e.min,t.units)),n.push(_(e.min,t.units));else if("max"===o)l.push(_(e.max,t.units)),n.push(_(e.max,t.units));else if("minmax"===o){const s=`${_(e.min,t.units)}–${_(e.max,t.units)}`;l.push(s),n.push(s)}}else if(o&&o.property){const t=s.get&&s.get(o.property);if(null==t||""===t)return;const e=o.label?`${o.label} `:"";o.asLink&&d(t)?(l.push(`${e}<a href="${c(t)}" target="_blank" rel="noopener">${c(o.linkText||o.label||t)}</a>`),n.push(`${o.label||""} ${t}`)):(l.push(c(e+t)),n.push(e+t))}}),this._statsEl.innerHTML=l.join(" · "),this._statsEl.setAttribute("title",n.join(" · ")),this._legendEl.innerHTML="",t.slope&&t.slopeLegend){const t=this._slopeScale();for(let e=0;e<=t.maxIdx;e++){const s=t.colorByIndex(e),o=t.capped&&e===t.maxIdx?`≥ ${e*t.classSize} %`:`${e*t.classSize}–${(e+1)*t.classSize} %`,i=document.createElement("span");i.className="oep-leg-it",i.innerHTML=`<i class="oep-sw" style="background:${s}"></i>${o}`,this._legendEl.appendChild(i)}this._legendEl.style.display=""}else this._legendEl.style.display="none"}_render(){const t=this.options,e=this._stats,s=this._samples;if(!s||!s.length)return;this._applyTheme();const o=this._applyPlacement();this._renderHeader();const i=t.margins,l=i.unit||"px",a=t=>"px"===l?t:t*(parseFloat(getComputedStyle(this.element).fontSize)||16),r=this._availWidth(),h="auto"===t.width||"100%"===t.width||"full"===t.width?r:Math.min("number"==typeof t.width?t.width:parseFloat(t.width)||r,r);this.element.style.width=o?"":`${h}px`;const p=this.element.clientWidth||(o?r:h),c=Math.max(220,p-16),d="number"==typeof t.height?t.height:180,u=a(i.top),m=a(i.right),_=a(i.bottom),f=a(i.left),g=Math.max(10,c-f-m),y=Math.max(10,d-u-_),x=null!=t.xTicks?t.xTicks:Math.max(2,Math.round(g/80)),b=null!=t.yTicks?t.yTicks:Math.max(2,Math.round(y/40));this._body.innerHTML="";const M=n.select(this._body).append("svg").attr("class","oep-svg").attr("width",c).attr("height",d).attr("viewBox",`0 0 ${c} ${d}`),k=M.append("g").attr("transform",`translate(${f},${u})`),z=n.scaleLinear().domain([0,e.distance]).range([0,g]),v=.1*(e.max-e.min)||10,E=n.scaleLinear().domain([e.min-v,e.max+v]).range([y,0]).nice();this._x=z,this._y=E,this._dims={innerW:g,innerH:y},t.grid&&k.append("g").attr("class","oep-grid").call(n.axisLeft(E).ticks(b).tickSize(-g).tickFormat(""));const C="imperial"===t.units?1609.344:1e3;const w=n.area().x(t=>z(t.x)).y0(y).y1(t=>E(t.z));if(t.slope){const e=this._slopeScale(),o=[];let i=1;for(;i<s.length;){const t=this._classIndex(s[i].slope,e);let l=i;for(;l+1<s.length&&this._classIndex(s[l+1].slope,e)===t;)l++;k.append("path").datum(s.slice(i-1,l+1)).attr("class","oep-area-slope").attr("fill",e.colorByIndex(t)).attr("d",w),i>1&&o.push(s[i-1]),i=l+1}t.slopeSeparators&&o.forEach(t=>{k.append("line").attr("class","oep-slope-sep").attr("x1",z(t.x)).attr("x2",z(t.x)).attr("y1",E(t.z)).attr("y2",y)})}else k.append("path").datum(s).attr("class","oep-area").attr("d",w);k.append("path").datum(s).attr("class","oep-line").attr("d",n.line().x(t=>z(t.x)).y(t=>E(t.z))),k.append("g").attr("class","oep-axis oep-axis-x").attr("transform",`translate(0,${y})`).call(n.axisBottom(z).ticks(x).tickFormat(t=>(t/C).toFixed(t/C<10?1:0))),k.append("text").attr("class","oep-axis-label").attr("x",g).attr("y",y+_-4).attr("text-anchor","end").text((t=>"imperial"===t?"mi":"km")(t.units)),k.append("g").attr("class","oep-axis oep-axis-y").call(n.axisLeft(E).ticks(b).tickFormat(e=>"imperial"===t.units?Math.round(3.28084*e):e)),t.zoom&&!this._cropMode&&[["A",this._zoomA],["B",this._zoomB]].forEach(([t,e])=>{if(null==e)return;const s=z(e);k.append("line").attr("class","oep-ab-line").attr("x1",s).attr("x2",s).attr("y1",0).attr("y2",y),k.append("text").attr("class","oep-ab-label").attr("x",s).attr("y",-6).attr("text-anchor","middle").text(t)});const S=k.append("g").attr("class","oep-focus").style("display","none");S.append("line").attr("class","oep-focus-line").attr("y1",0).attr("y2",y),S.append("circle").attr("class","oep-focus-dot").attr("r",4);const $=S.append("g").attr("class","oep-focus-label");$.append("rect").attr("class","oep-focus-bg"),$.append("text").attr("class","oep-focus-txt"),this._focus=S;const B=n.bisector(t=>t.x).left,L=t=>{const s=n.pointer(t,k.node())[0];return Math.max(0,Math.min(e.distance,z.invert(s)))},T=M.append("rect").attr("class","oep-overlay").attr("x",f).attr("y",u).attr("width",g).attr("height",y);T.on("mousemove",t=>{const e=L(t),o=B(s,e,1),i=s[o-1],l=s[o]||i,n=e-i.x>l.x-e?l:i;this._setFocus(n),this._marker&&this._marker.setPosition(n.coord)}).on("mouseout",()=>this._clearFocus()),T.on("click",t=>{if(!this._armed||this._cropMode)return;const e=L(t);"A"===this._armed?this._zoomA=e:this._zoomB=e,this._armed=null,this._updateZoomButtons(),null!=this._zoomA&&null!=this._zoomB?this._applyZoom():this._render()}),this._armed&&T.style("cursor","col-resize"),this._adjustAttribution()}_arm(t){this._armed=this._armed===t?null:t,this._updateZoomButtons(),this._focus&&this._render()}_updateZoomButtons(){const t=!!this.options.zoom&&!!this._feature;this._toolbar.style.display=t?"":"none";const e=this._cropMode;this._btnA.style.display=t&&!e?"":"none",this._btnB.style.display=t&&!e?"":"none",this._btnAll.style.display=t&&e?"":"none",this._btnA.classList.toggle("armed","A"===this._armed),this._btnB.classList.toggle("armed","B"===this._armed)}_applyZoom(){const t=Math.min(this._zoomA,this._zoomB),e=Math.max(this._zoomA,this._zoomB),s=this._fullSamples.filter(s=>s.x>=t&&s.x<=e);if(s.length<2)return;const o=s[0].x,i=s.map(t=>({x:t.x-o,z:t.z,coord:t.coord,slope:t.slope})),n=s.map(t=>t.coord);this._samples=i,this._stats=this._statsOf(i,!1),this._cropMode=!0,this._updateZoomButtons(),this._render();const a=this.getMap();if(a){const t=l(n),e=a.getView();try{this._fitRes=e.getResolutionForExtent(t,a.getSize())}catch(t){this._fitRes=null}e.fit(t,{padding:[40,40,40,40],duration:400})}}_exitZoom(){this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._samples=this._fullSamples,this._stats=this._fullStats,this._updateZoomButtons(),this._collapsed||this._render();const t=this.getMap();t&&this._feature&&t.getView().fit(this._feature.getGeometry().getExtent(),{padding:[40,40,40,40],duration:400})}_tooltipText(t){const e=this.options,s=[];return e.tooltipItems.forEach(o=>{var i;"distance"===o?s.push(m(t.x,e.units)):"elevation"===o?s.push(_(t.z,e.units)):"slope"===o&&s.push(`${(i=t.slope||0)>=0?"+":""}${i.toFixed(1)} %`)}),s.join(" · ")}_setFocus(t){if(!this._focus)return;const e=this._x,s=this._y;this._focus.style("display",null),this._focus.select(".oep-focus-line").attr("x1",e(t.x)).attr("x2",e(t.x)),this._focus.select(".oep-focus-dot").attr("cx",e(t.x)).attr("cy",s(t.z));let o=s(t.z)-16;o<12&&(o=s(t.z)+22);const i=this._focus.select(".oep-focus-txt").attr("x",e(t.x)).attr("y",o).text(this._tooltipText(t)),l=i.node().getBBox();this._focus.select(".oep-focus-bg").attr("x",l.x-4).attr("y",l.y-2).attr("width",l.width+8).attr("height",l.height+4),e(t.x)+l.width/2>this._dims.innerW?i.attr("text-anchor","end"):e(t.x)-l.width/2<0?i.attr("text-anchor","start"):i.attr("text-anchor","middle")}_focusByCoord(t){const e=this._samples;if(!e)return;let s=null,o=1/0;for(const i of e){const e=i.coord[0]-t[0],l=i.coord[1]-t[1],n=e*e+l*l;n<o&&(o=n,s=i)}s&&(this._setFocus(s),this._marker&&this._marker.setPosition(s.coord))}_clearFocus(){this._focus&&this._focus.style("display","none"),this._marker&&this._marker.setPosition(void 0)}_clear(){this._body.innerHTML="",this._legendEl.innerHTML="",this._legendEl.style.display="none",this._titleEl.textContent=this.options.labels.empty,this._titleEl.removeAttribute("title"),this._statsEl.innerHTML="",this._statsEl.removeAttribute("title"),this._cropMode=!1,this._updateZoomButtons(),this._clearFocus(),this.element.style.display="none",this._adjustAttribution()}}y.addTheme=(t,e)=>{a[t]=e},y.THEMES=a,y.POSITIONS=r,y.version="0.5.0";export{y as default};
16
+ */const a={steelblue:{area:"#4682b4",line:"#3a6d96",axis:"#555",text:"#222",focus:"#e6550d"},lime:{area:"#9ccc2f",line:"#7da521",axis:"#555",text:"#222",focus:"#d62728"},purple:{area:"#9467bd",line:"#76529c",axis:"#555",text:"#222",focus:"#ff9f1c"},slate:{area:"#7c8a99",line:"#4a5560",axis:"#5a6570",text:"#26303a",focus:"#2f81f7"},graphite:{area:"#9a948c",line:"#5c574f",axis:"#5c574f",text:"#2b2823",focus:"#e07a3f"},amber:{area:"#f0a23b",line:"#c4671a",axis:"#7a5a36",text:"#3a2a16",focus:"#1f6fb2"}},r=["top","bottom","left","right","top-left","top-right","bottom-left","bottom-right"],h={immersion:"docked",position:"bottom",width:520,height:180,margins:{unit:"px",top:20,right:24,bottom:30,left:48},units:"meters",dataProjection:null,maxPoints:2e3,smoothing:0,theme:"steelblue",color:null,trackLayer:null,transparency:!1,transparencyLevel:.45,grid:!0,slope:!1,slopeClassSize:2.5,slopeColors:null,slopeSeparators:!0,slopeLegend:!0,maxClasses:8,xTicks:null,yTicks:null,show:"click",collapsable:!0,collapsed:!1,followMap:!0,marker:!0,hideOnMapClick:!0,responsive:!0,mobileBreakpoint:640,zoom:!1,ignoreStops:!0,stopSpeed:.5,tooltipItems:["distance","elevation"],headerItems:["distance","ascent","descent","minmax"],titleProperty:"name",titleLink:null,labels:{distance:"Distance",elevation:"Altitude",slope:"Pente",ascent:"D+",descent:"D-",empty:"Cliquez un tracé",time:"Temps",duration:"Durée",durationUnits:{s:"sec",m:"min",h:"h",d:"j"},zoomStart:"Définir le début (A)",zoomEnd:"Définir la fin (B)",zoomAll:"Tout voir"}};function p(t,e){const s={};return Object.keys(t).forEach(e=>{s[e]=t[e]}),e&&Object.keys(e).forEach(o=>{e[o]&&"object"==typeof e[o]&&!Array.isArray(e[o])&&t[o]&&"object"==typeof t[o]?s[o]=p(t[o],e[o]):s[o]=e[o]}),s}const c=t=>String(t).replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[t])),u=t=>"string"==typeof t&&/^(https?:)?\/\/|^mailto:/i.test(t);function d(t,e){const s=t&&t.getProperties?t.getProperties():{};let o=s.coordTimes;null==o&&s.coordinateProperties&&(o=s.coordinateProperties.times||s.coordinateProperties.coordTimes);let i=null;if(Array.isArray(o)&&(i=Array.isArray(o[0])?o.reduce((t,e)=>t.concat(e),[]):o.slice()),!i&&e){const t=[];let s=!1;for(const o of e)for(const e of o){const o=e.length>3?e[3]:null;t.push(o),null!=o&&isFinite(o)&&(s=!0)}i=s?t:null}if(!i)return null;let n=0;const l=i.map(t=>{if(null==t||""===t)return null;const e="number"==typeof t?t>1e12?t:1e3*t:Date.parse(t);return isFinite(e)?(n++,e):null});return n>=2?l:null}function m(t,e,s){let o=t;if("function"==typeof o)try{o=o(e,s)}catch(t){return null}if(Array.isArray(o)&&(o=o[0]),o&&o.getStroke){const t=o.getStroke();if(t)return t.getColor()}return null}const _=(t,e)=>{if("imperial"===e){const e=t/1609.344;return e<.2?`${Math.round(3.28084*t)} ft`:`${e.toFixed(e<10?2:1)} mi`}return t<1e3?`${Math.round(t)} m`:`${(t/1e3).toFixed(t<1e4?2:1)} km`},f=(t,e)=>"imperial"===e?`${Math.round(3.28084*t)} ft`:`${Math.round(t)} m`;const g='<svg viewBox="0 0 24 24"><path d="M5 13h14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>',y='<svg viewBox="0 0 24 24"><path d="M3 19l5-7 4 4 4-6 5 9z" fill="currentColor" opacity=".85"/></svg>';class x extends t{constructor(t){const e=p(h,t||{});-1===r.indexOf(e.position)&&(e.position="bottom");const s=function(t){const e=document.createElement("div");return e.className=`ol-elevation-profile ol-unselectable ol-control oep-pos-${t.position} oep-theme-${"string"==typeof t.theme?t.theme:"custom"}`+("floating"===t.immersion?" oep-floating":""),e}(e);super({element:s,target:t&&t.target}),this.options=e,this.slopeColors=e.slopeColors||null,this._feature=null,this._fullSamples=null,this._fullStats=null,this._samples=null,this._stats=null,this._marker=null,this._collapsed=!!e.collapsed,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._onResize=()=>{this._feature&&!this._collapsed&&this._render()},this._buildDom(s),s.style.display="none"}static featureHasZ(t){const e=t&&t.getGeometry&&t.getGeometry();if(!e)return!1;const s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()];for(const t of s)for(const e of t)if(e.length>2&&isFinite(e[2]))return!0;return!1}static featureHasTime(t){const e=t&&t.getGeometry&&t.getGeometry();if(!e)return!1;return!!d(t,"MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()])}_buildDom(t){const e=this.options;this._applyTheme(),this._applyTransparency();const s=document.createElement("div");s.className="oep-header",this._titleEl=document.createElement("span"),this._titleEl.className="oep-title",this._titleEl.textContent=e.labels.empty,this._statsEl=document.createElement("span"),this._statsEl.className="oep-stats",s.appendChild(this._titleEl),s.appendChild(this._statsEl),this._toolbar=document.createElement("span"),this._toolbar.className="oep-toolbar";const o=(t,e,s,o)=>{const i=document.createElement("button");return i.type="button",i.className=`oep-tbtn ${t}`,i.innerHTML=e,i.title=s,i.setAttribute("aria-label",s),i.addEventListener("click",o),this._toolbar.appendChild(i),i};this._btnA=o("oep-a",'<svg viewBox="0 0 24 24"><path d="M7 5v14M11 12h8m0 0-3-3m3 3-3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomStart,()=>this._arm("A")),this._btnB=o("oep-b",'<svg viewBox="0 0 24 24"><path d="M17 5v14M13 12H5m0 0 3-3m-3 3 3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomEnd,()=>this._arm("B")),this._btnAll=o("oep-all",'<svg viewBox="0 0 24 24"><path d="M4 12h16M4 12l4-4M4 12l4 4M20 12l-4-4M20 12l-4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomAll,()=>this._exitZoom()),s.appendChild(this._toolbar);const i=document.createElement("button");i.className="oep-toggle",i.type="button",i.setAttribute("aria-label","Réduire ou agrandir le profil"),i.innerHTML=this._collapsed?y:g,i.addEventListener("click",()=>this.toggleCollapsed()),s.appendChild(i),this._toggleBtn=i,this._legendEl=document.createElement("div"),this._legendEl.className="oep-legend",this._legendEl.style.display="none",this._body=document.createElement("div"),this._body.className="oep-body",t.appendChild(s),t.appendChild(this._legendEl),t.appendChild(this._body),this._applyCollapsable(),this._updateZoomButtons(),this._collapsed&&t.classList.add("oep-collapsed")}_resolveColor(){const t=this.options;return t.color?"auto"===t.color?this._featureColor():t.color:null}_featureColor(){const t=this._feature;if(!t)return null;const e=this.getMap()?this.getMap().getView().getResolution():1;let s=m(t.getStyle&&t.getStyle(),t,e);return!s&&this.options.trackLayer&&(s=m(this.options.trackLayer.getStyle&&this.options.trackLayer.getStyle(),t,e)),function(t){if(null==t)return null;if("string"==typeof t)return t;if(Array.isArray(t)){const e=t.length>3?t[3]:1;return`rgba(${0|t[0]},${0|t[1]},${0|t[2]},${e})`}return null}(s)}_applyTheme(){const t=this.options,e="object"==typeof t.theme?t.theme:a[t.theme]||a.steelblue;this.themeColors=e;const s=this.element,o=this._resolveColor(),i=o||e.area;let n=e.line;if(o)try{n=String(l.color(o).darker(.7))}catch(t){n=o}s.style.setProperty("--oep-area",i),s.style.setProperty("--oep-line",n),s.style.setProperty("--oep-axis",e.axis),s.style.setProperty("--oep-text",e.text),s.style.setProperty("--oep-focus",e.focus)}_applyTransparency(){const t=this.options.transparency;let e;e=!1===t||null==t?1:!0===t?this.options.transparencyLevel:Math.max(0,Math.min(1,+t)),this.element.style.setProperty("--oep-bg",`rgba(255,255,255,${e})`),this.element.classList.toggle("oep-transparent",e<1)}_applyCollapsable(){const t=this.options.collapsable;this._toggleBtn.style.display=t?"":"none",!t&&this._collapsed&&this.toggleCollapsed(!1)}toggleCollapsed(t){this._collapsed="boolean"==typeof t?t:!this._collapsed,this.element.classList.toggle("oep-collapsed",this._collapsed),this._toggleBtn.innerHTML=this._collapsed?y:g,!this._collapsed&&this._feature&&this._render(),this._adjustAttribution()}_availWidth(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement();return e&&e.clientWidth||("undefined"!=typeof window?window.innerWidth:1024)}_isMobile(){return this.options.responsive&&"undefined"!=typeof window&&window.innerWidth<=(this.options.mobileBreakpoint||640)}_applyPlacement(){const t=this._isMobile();let e=this.options.position;return t&&(e=/top/.test(e)?"top":"bottom"),this.element.className=this.element.className.replace(/oep-pos-\S+/,`oep-pos-${e}`),this.element.classList.toggle("oep-mobile",t),t}_adjustAttribution(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement(),s=e&&e.querySelector&&e.querySelector(".ol-attribution");if(!s)return;if(s.style.bottom="",s.style.right="","none"===this.element.style.display||this._collapsed)return;const o=e.getBoundingClientRect(),i=this.element.getBoundingClientRect();if(!i.height)return;const n=o.bottom-i.bottom,l=o.right-i.right;n<i.height&&l<24&&(s.style.right=`${Math.max(0,Math.round(l))}px`,s.style.bottom=`${Math.round(i.height+2*n)}px`)}setMap(t){const o=this.getMap();if(super.setMap(t),this._mapKeys&&(this._mapKeys.forEach(s),this._mapKeys=null),o&&"undefined"!=typeof window&&window.removeEventListener("resize",this._onResize),this._marker&&!t&&this._marker.setPosition(void 0),!t)return;const i=this.options;if(i.dataProjection||(i.dataProjection=t.getView().getProjection()),i.marker&&!this._marker){const s=document.createElement("div");s.className="oep-marker",this._marker=new e({element:s,positioning:"center-center",stopEvent:!1}),t.addOverlay(this._marker)}"undefined"!=typeof window&&window.addEventListener("resize",this._onResize);const n=e=>t.forEachFeatureAtPixel(e,t=>{const e=t.getGeometry();return e&&/LineString/.test(e.getType())?t:void 0});this._mapKeys=[],this._mapKeys.push(t.on("mouseover"===i.show?"pointermove":"click",t=>{const e=n(t.pixel);e&&e!==this._feature&&this.setFeature(e)})),i.hideOnMapClick&&this._mapKeys.push(t.on("click",t=>{!n(t.pixel)&&this._feature&&this.clear()})),i.followMap&&this._mapKeys.push(t.on("pointermove",e=>{if(!this._feature||this._collapsed)return;const s=this._feature.getGeometry().getClosestPoint(e.coordinate),o=t.getPixelFromCoordinate(s);o&&(Math.hypot(o[0]-e.pixel[0],o[1]-e.pixel[1])<14?this._focusByCoord(s):this._clearFocus())})),this._mapKeys.push(t.on("moveend",()=>{this._cropMode&&this._fitRes&&t.getView().getResolution()>1.25*this._fitRes&&this._exitZoom()})),this._mapKeys.push(t.on("change:size",this._onResize))}setFeature(t){return this._feature=t||null,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,t?(this.element.style.display="",this._compute(),this._updateZoomButtons(),this._collapsed||this._render(),this):(this._fullSamples=this._samples=null,this._clear(),this)}clear(){return this.setFeature(null)}getStats(){return this._stats}setTheme(t){return this.options.theme=t,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setColor(t){return this.options.color=t||null,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setOptions(t){return this.options=p(this.options,t||{}),t&&"slopeColors"in t&&(this.slopeColors=t.slopeColors||null),t&&(t.theme||"color"in t)&&this._applyTheme(),t&&("transparency"in t||"transparencyLevel"in t)&&this._applyTransparency(),t&&"collapsable"in t&&this._applyCollapsable(),t&&"zoom"in t&&this._updateZoomButtons(),t&&void 0!==t.width&&"number"==typeof t.width&&(this.options.width=t.width),this._feature&&(this._compute(),this._updateZoomButtons(),this._collapsed||this._render()),this}_compute(){const t=this.options,e=this._feature.getGeometry(),s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()],n=t.dataProjection||this.getMap()&&this.getMap().getView().getProjection()||"EPSG:3857",l=d(this._feature,s);this._hasTime=!!l;const a=!1!==t.ignoreStops,r=null!=t.stopSpeed?t.stopSpeed:.5,h=[];let p=0,c=null,u=-1,m=0,_=null;for(const t of s)for(const e of t){u++;const t=o(e,n);let s=0;c&&(s=i(c,t),p+=s);let d=null;if(l&&null!=l[u]){const t=l[u];if(null!=_){const e=(t-_)/1e3;e>0&&(!a||s/e>=r)&&(m+=e)}d=m,_=t}c=t,h.push({x:p,z:e.length>2&&isFinite(e[2])?e[2]:0,coord:e,t:d})}t.smoothing>0&&this._smooth(h,t.smoothing);const f=this._decimate(h,t.maxPoints);this._addSlope(f),this._fullSamples=f,this._fullStats=this._statsOf(f,!0),this._samples=f,this._stats=this._fullStats}_statsOf(t,e){let s=0,o=0,i=1/0,n=-1/0,l=0;for(let e=0;e<t.length;e++){const a=t[e].z;if(a<i&&(i=a),a>n&&(n=a),e>0){const i=a-t[e-1].z;i>0?s+=i:o-=i}const r=Math.abs(t[e].slope||0);r>l&&(l=r)}const a=t.length?t[t.length-1].x-t[0].x:0,r=t.length?t[0].t:null,h=t.length?t[t.length-1].t:null;return{distance:a,duration:null!=r&&null!=h?h-r:null,ascent:s,descent:o,min:isFinite(i)?i:0,max:isFinite(n)?n:0,maxAbsSlope:l,points:t.length}}_smooth(t,e){if(!(e>0)||t.length<3)return;const s=e/2,o=t.map(t=>t.z);let i=0,n=0,l=0;for(let e=0;e<t.length;e++){const a=t[e].x;for(;i<t.length&&t[i].x<a-s;)l-=o[i],i++;for(;n<t.length&&t[n].x<=a+s;)l+=o[n],n++;t[e].z=n>i?l/(n-i):o[e]}}_decimate(t,e){const s=t=>({x:t.x,z:t.z,coord:t.coord,t:t.t});if(!e||t.length<=e)return t.map(s);const o=t.length/e,i=[];for(let n=0;n<e;n++)i.push(s(t[Math.floor(n*o)]));return i.push(s(t[t.length-1])),i}_addSlope(t){for(let e=1;e<t.length;e++){const s=t[e].x-t[e-1].x;t[e].slope=s>0?(t[e].z-t[e-1].z)/s*100:0}t.length&&(t[0].slope=t.length>1?t[1].slope:0)}_slopeScale(){const t=this.options.slopeClassSize||2.5,e=this.options.maxClasses||8,s=Math.max(1,Math.floor((this._stats.maxAbsSlope||0)/t)),o=Math.min(s,e-1),i=s>o;let n;if(this.slopeColors&&this.slopeColors.length){const t=l.interpolateRgbBasis(this.slopeColors);n=e=>t(o?e/o:0)}else{const t=l.scaleLinear().domain([0,.16,.42,.68,1]).range(["#2166ac","#27a35a","#ffe000","#f4791f","#d7191c"]).interpolate(l.interpolateRgb).clamp(!0);n=e=>String(t(o?e/o:0))}return{classSize:t,maxIdx:o,capped:i,colorByIndex:n}}_classIndex(t,e){return Math.min(e.maxIdx,Math.floor(Math.abs(t)/e.classSize))}_fmtDuration(t){if(null==t||!isFinite(t))return"";const e=this.options.labels&&this.options.labels.durationUnits||{s:"sec",m:"min",h:"h",d:"j"};if((t=Math.max(0,Math.round(t)))<60)return t+" "+e.s;if(t<3600)return Math.round(t/60)+" "+e.m;if(t<86400){let s=Math.floor(t/3600),o=Math.round(t%3600/60);return 60===o&&(s++,o=0),o?`${s} ${e.h} ${o} ${e.m}`:`${s} ${e.h}`}let s=Math.floor(t/86400),o=Math.round(t%86400/3600);return 24===o&&(s++,o=0),o?`${s} ${e.d} ${o} ${e.h}`:`${s} ${e.d}`}_renderHeader(){const t=this.options,e=this._stats,s=this._feature,o=s.get&&s.get(t.titleProperty)||"Profil",i=t.titleLink&&s.get&&s.get(t.titleLink);u(i)?this._titleEl.innerHTML=`<a href="${c(i)}" target="_blank" rel="noopener">${c(o)}</a>`:this._titleEl.textContent=o,this._titleEl.setAttribute("title",o);const n=[],l=[];if(t.headerItems.forEach(o=>{if("string"==typeof o){if("distance"===o)n.push(`<b>${_(e.distance,t.units)}</b>`),l.push(_(e.distance,t.units));else if("ascent"===o)n.push(`<span class="oep-up">${t.labels.ascent} ${f(e.ascent,t.units)}</span>`),l.push(`${t.labels.ascent} ${f(e.ascent,t.units)}`);else if("descent"===o)n.push(`<span class="oep-down">${t.labels.descent} ${f(e.descent,t.units)}</span>`),l.push(`${t.labels.descent} ${f(e.descent,t.units)}`);else if("min"===o)n.push(f(e.min,t.units)),l.push(f(e.min,t.units));else if("max"===o)n.push(f(e.max,t.units)),l.push(f(e.max,t.units));else if("minmax"===o){const s=`${f(e.min,t.units)}–${f(e.max,t.units)}`;n.push(s),l.push(s)}else if("duration"===o&&null!=e.duration){const s=this._fmtDuration(e.duration);n.push(`<span class="oep-time">${c(t.labels.duration)} ${s}</span>`),l.push(`${t.labels.duration} ${s}`)}}else if(o&&o.property){const t=s.get&&s.get(o.property);if(null==t||""===t)return;const e=o.label?`${o.label} `:"";o.asLink&&u(t)?(n.push(`${e}<a href="${c(t)}" target="_blank" rel="noopener">${c(o.linkText||o.label||t)}</a>`),l.push(`${o.label||""} ${t}`)):(n.push(c(e+t)),l.push(e+t))}}),this._statsEl.innerHTML=n.join(" · "),this._statsEl.setAttribute("title",l.join(" · ")),this._legendEl.innerHTML="",t.slope&&t.slopeLegend){const t=this._slopeScale();for(let e=0;e<=t.maxIdx;e++){const s=t.colorByIndex(e),o=t.capped&&e===t.maxIdx?`≥ ${e*t.classSize} %`:`${e*t.classSize}–${(e+1)*t.classSize} %`,i=document.createElement("span");i.className="oep-leg-it",i.innerHTML=`<i class="oep-sw" style="background:${s}"></i>${o}`,this._legendEl.appendChild(i)}this._legendEl.style.display=""}else this._legendEl.style.display="none"}_render(){const t=this.options,e=this._stats,s=this._samples;if(!s||!s.length)return;this._applyTheme();const o=this._applyPlacement();this._renderHeader();const i=t.margins,n=i.unit||"px",a=t=>"px"===n?t:t*(parseFloat(getComputedStyle(this.element).fontSize)||16),r=this._availWidth(),h="auto"===t.width||"100%"===t.width||"full"===t.width?r:Math.min("number"==typeof t.width?t.width:parseFloat(t.width)||r,r);this.element.style.width=o?"":`${h}px`;const p=this.element.clientWidth||(o?r:h),c=Math.max(220,p-16),u="number"==typeof t.height?t.height:180,d=a(i.top),m=a(i.right),_=a(i.bottom),f=a(i.left),g=Math.max(10,c-f-m),y=Math.max(10,u-d-_),x=null!=t.xTicks?t.xTicks:Math.max(2,Math.round(g/80)),b=null!=t.yTicks?t.yTicks:Math.max(2,Math.round(y/40));this._body.innerHTML="";const M=l.select(this._body).append("svg").attr("class","oep-svg").attr("width",c).attr("height",u).attr("viewBox",`0 0 ${c} ${u}`),k=M.append("g").attr("transform",`translate(${f},${d})`),$=l.scaleLinear().domain([0,e.distance]).range([0,g]),z=.1*(e.max-e.min)||10,v=l.scaleLinear().domain([e.min-z,e.max+z]).range([y,0]).nice();this._x=$,this._y=v,this._dims={innerW:g,innerH:y},t.grid&&k.append("g").attr("class","oep-grid").call(l.axisLeft(v).ticks(b).tickSize(-g).tickFormat(""));const E="imperial"===t.units?1609.344:1e3;const C=l.area().x(t=>$(t.x)).y0(y).y1(t=>v(t.z));if(t.slope){const e=this._slopeScale(),o=[];let i=1;for(;i<s.length;){const t=this._classIndex(s[i].slope,e);let n=i;for(;n+1<s.length&&this._classIndex(s[n+1].slope,e)===t;)n++;k.append("path").datum(s.slice(i-1,n+1)).attr("class","oep-area-slope").attr("fill",e.colorByIndex(t)).attr("d",C),i>1&&o.push(s[i-1]),i=n+1}t.slopeSeparators&&o.forEach(t=>{k.append("line").attr("class","oep-slope-sep").attr("x1",$(t.x)).attr("x2",$(t.x)).attr("y1",v(t.z)).attr("y2",y)})}else k.append("path").datum(s).attr("class","oep-area").attr("d",C);k.append("path").datum(s).attr("class","oep-line").attr("d",l.line().x(t=>$(t.x)).y(t=>v(t.z))),k.append("g").attr("class","oep-axis oep-axis-x").attr("transform",`translate(0,${y})`).call(l.axisBottom($).ticks(x).tickFormat(t=>(t/E).toFixed(t/E<10?1:0))),k.append("text").attr("class","oep-axis-label").attr("x",g).attr("y",y+_-4).attr("text-anchor","end").text((t=>"imperial"===t?"mi":"km")(t.units)),k.append("g").attr("class","oep-axis oep-axis-y").call(l.axisLeft(v).ticks(b).tickFormat(e=>"imperial"===t.units?Math.round(3.28084*e):e)),t.zoom&&!this._cropMode&&[["A",this._zoomA],["B",this._zoomB]].forEach(([t,e])=>{if(null==e)return;const s=$(e);k.append("line").attr("class","oep-ab-line").attr("x1",s).attr("x2",s).attr("y1",0).attr("y2",y),k.append("text").attr("class","oep-ab-label").attr("x",s).attr("y",-6).attr("text-anchor","middle").text(t)});const w=k.append("g").attr("class","oep-focus").style("display","none");w.append("line").attr("class","oep-focus-line").attr("y1",0).attr("y2",y),w.append("circle").attr("class","oep-focus-dot").attr("r",4);const S=w.append("g").attr("class","oep-focus-label");S.append("rect").attr("class","oep-focus-bg"),S.append("text").attr("class","oep-focus-txt"),this._focus=w;const B=l.bisector(t=>t.x).left,T=t=>{const s=l.pointer(t,k.node())[0];return Math.max(0,Math.min(e.distance,$.invert(s)))},A=M.append("rect").attr("class","oep-overlay").attr("x",f).attr("y",d).attr("width",g).attr("height",y);A.on("mousemove",t=>{const e=T(t),o=B(s,e,1),i=s[o-1],n=s[o]||i,l=e-i.x>n.x-e?n:i;this._setFocus(l),this._marker&&this._marker.setPosition(l.coord)}).on("mouseout",()=>this._clearFocus()),A.on("click",t=>{if(!this._armed||this._cropMode)return;const e=T(t);"A"===this._armed?this._zoomA=e:this._zoomB=e,this._armed=null,this._updateZoomButtons(),null!=this._zoomA&&null!=this._zoomB?this._applyZoom():this._render()}),this._armed&&A.style("cursor","col-resize"),this._adjustAttribution()}_arm(t){this._armed=this._armed===t?null:t,this._updateZoomButtons(),this._focus&&this._render()}_updateZoomButtons(){const t=!!this.options.zoom&&!!this._feature;this._toolbar.style.display=t?"":"none";const e=this._cropMode;this._btnA.style.display=t&&!e?"":"none",this._btnB.style.display=t&&!e?"":"none",this._btnAll.style.display=t&&e?"":"none",this._btnA.classList.toggle("armed","A"===this._armed),this._btnB.classList.toggle("armed","B"===this._armed)}_applyZoom(){const t=Math.min(this._zoomA,this._zoomB),e=Math.max(this._zoomA,this._zoomB),s=this._fullSamples.filter(s=>s.x>=t&&s.x<=e);if(s.length<2)return;const o=s[0].x,i=null!=s[0].t?s[0].t:0,l=s.map(t=>({x:t.x-o,z:t.z,coord:t.coord,slope:t.slope,t:null!=t.t?t.t-i:null})),a=s.map(t=>t.coord);this._samples=l,this._stats=this._statsOf(l,!1),this._cropMode=!0,this._updateZoomButtons(),this._render();const r=this.getMap();if(r){const t=n(a),e=r.getView();try{this._fitRes=e.getResolutionForExtent(t,r.getSize())}catch(t){this._fitRes=null}e.fit(t,{padding:[40,40,40,40],duration:400})}}_exitZoom(){this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._samples=this._fullSamples,this._stats=this._fullStats,this._updateZoomButtons(),this._collapsed||this._render();const t=this.getMap();t&&this._feature&&t.getView().fit(this._feature.getGeometry().getExtent(),{padding:[40,40,40,40],duration:400})}_tooltipText(t){const e=this.options,s=[];return e.tooltipItems.forEach(o=>{var i;"distance"===o?s.push(_(t.x,e.units)):"elevation"===o?s.push(f(t.z,e.units)):"slope"===o?s.push(`${(i=t.slope||0)>=0?"+":""}${i.toFixed(1)} %`):"time"===o&&null!=t.t&&s.push(this._fmtDuration(t.t))}),s.join(" · ")}_setFocus(t){if(!this._focus)return;const e=this._x,s=this._y;this._focus.style("display",null),this._focus.select(".oep-focus-line").attr("x1",e(t.x)).attr("x2",e(t.x)),this._focus.select(".oep-focus-dot").attr("cx",e(t.x)).attr("cy",s(t.z));let o=s(t.z)-16;o<12&&(o=s(t.z)+22);const i=this._focus.select(".oep-focus-txt").attr("x",e(t.x)).attr("y",o).text(this._tooltipText(t)),n=i.node().getBBox();this._focus.select(".oep-focus-bg").attr("x",n.x-4).attr("y",n.y-2).attr("width",n.width+8).attr("height",n.height+4),e(t.x)+n.width/2>this._dims.innerW?i.attr("text-anchor","end"):e(t.x)-n.width/2<0?i.attr("text-anchor","start"):i.attr("text-anchor","middle")}_focusByCoord(t){const e=this._samples;if(!e)return;let s=null,o=1/0;for(const i of e){const e=i.coord[0]-t[0],n=i.coord[1]-t[1],l=e*e+n*n;l<o&&(o=l,s=i)}s&&(this._setFocus(s),this._marker&&this._marker.setPosition(s.coord))}_clearFocus(){this._focus&&this._focus.style("display","none"),this._marker&&this._marker.setPosition(void 0)}_clear(){this._body.innerHTML="",this._legendEl.innerHTML="",this._legendEl.style.display="none",this._titleEl.textContent=this.options.labels.empty,this._titleEl.removeAttribute("title"),this._statsEl.innerHTML="",this._statsEl.removeAttribute("title"),this._cropMode=!1,this._updateZoomButtons(),this._clearFocus(),this.element.style.display="none",this._adjustAttribution()}}x.addTheme=(t,e)=>{a[t]=e},x.THEMES=a,x.POSITIONS=r,x.version="0.6.0";export{x as default};
@@ -72,7 +72,6 @@
72
72
  slopeSeparators: true, // ligne verticale à chaque changement de classe
73
73
  slopeLegend: true,
74
74
  maxClasses: 8, // nombre maximal de classes de pente (couleurs + légende)
75
- maxLegendClasses: 8,
76
75
  xTicks: null,
77
76
  yTicks: null,
78
77
  show: 'click',
@@ -84,6 +83,8 @@
84
83
  responsive: true, // adapte la largeur/placement, mobile inclus
85
84
  mobileBreakpoint: 640, // <= largeur écran -> mode mobile (100% largeur, top/bottom)
86
85
  zoom: false, // boutons début/fin pour recadrer carte + profil sur A..B
86
+ ignoreStops: true, // durée = temps en mouvement (ignore les arrêts)
87
+ stopSpeed: 0.5, // seuil d'arrêt en m/s (~1,8 km/h)
87
88
  tooltipItems: ['distance', 'elevation'],
88
89
  headerItems: ['distance', 'ascent', 'descent', 'minmax'],
89
90
  titleProperty: 'name',
@@ -91,6 +92,8 @@
91
92
  labels: {
92
93
  distance: 'Distance', elevation: 'Altitude', slope: 'Pente',
93
94
  ascent: 'D+', descent: 'D-', empty: 'Cliquez un tracé',
95
+ time: 'Temps', duration: 'Durée',
96
+ durationUnits: { s: 'sec', m: 'min', h: 'h', d: 'j' },
94
97
  zoomStart: 'Définir le début (A)', zoomEnd: 'Définir la fin (B)', zoomAll: 'Tout voir'
95
98
  }
96
99
  };
@@ -108,6 +111,30 @@
108
111
  const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
109
112
  const isUrl = (v) => typeof v === 'string' && /^(https?:)?\/\/|^mailto:/i.test(v);
110
113
 
114
+ // Extrait un tableau plat de timestamps (ms epoch) aligné sur l'ordre plat des coordonnées,
115
+ // depuis properties.coordTimes (ISO ou nombre), coordinateProperties.times, ou la 4e dimension (M).
116
+ function extractTimes(feature, lines) {
117
+ const props = (feature && feature.getProperties) ? feature.getProperties() : {};
118
+ let raw = props.coordTimes;
119
+ if (raw == null && props.coordinateProperties) raw = props.coordinateProperties.times || props.coordinateProperties.coordTimes;
120
+ let flat = null;
121
+ if (Array.isArray(raw)) flat = Array.isArray(raw[0]) ? raw.reduce((a, b) => a.concat(b), []) : raw.slice();
122
+ if (!flat && lines) { // repli : 4e dimension M (layout XYZM)
123
+ const tmp = []; let any = false;
124
+ for (const seg of lines) for (const c of seg) { const v = c.length > 3 ? c[3] : null; tmp.push(v); if (v != null && isFinite(v)) any = true; }
125
+ flat = any ? tmp : null;
126
+ }
127
+ if (!flat) return null;
128
+ let valid = 0;
129
+ const ms = flat.map((v) => {
130
+ if (v == null || v === '') return null;
131
+ const t = (typeof v === 'number') ? (v > 1e12 ? v : v * 1000) : Date.parse(v); // epoch ms / epoch s / ISO
132
+ if (!isFinite(t)) return null;
133
+ valid++; return t;
134
+ });
135
+ return valid >= 2 ? ms : null;
136
+ }
137
+
111
138
  function colorToCss(c) {
112
139
  if (c == null) return null;
113
140
  if (typeof c === 'string') return c;
@@ -178,8 +205,10 @@
178
205
  * @property {boolean} [responsive=true] Adapt width/placement; mobile included.
179
206
  * @property {number} [mobileBreakpoint=640] Below this width: mobile mode (100% width, top/bottom only).
180
207
  * @property {boolean} [zoom=false] A/B buttons to crop map + profile to a sub-range.
181
- * @property {Array<'distance'|'elevation'|'slope'>} [tooltipItems=['distance','elevation']] Tooltip content.
182
- * @property {Array<string|{property:string,label?:string,asLink?:boolean,linkText?:string}>} [headerItems] Header content.
208
+ * @property {boolean} [ignoreStops=true] When computing time, ignore stopped segments (moving time).
209
+ * @property {number} [stopSpeed=0.5] Speed threshold (m/s) below which a segment counts as a stop.
210
+ * @property {Array<'distance'|'elevation'|'slope'|'time'>} [tooltipItems=['distance','elevation']] Tooltip content (`'time'` = elapsed time at the cursor, if the track has time data).
211
+ * @property {Array<string|{property:string,label?:string,asLink?:boolean,linkText?:string}>} [headerItems] Header content (string tokens: distance, ascent, descent, min, max, minmax, `'duration'` = total elapsed time).
183
212
  * @property {string} [titleProperty='name'] Feature property used as the title.
184
213
  * @property {?string} [titleLink=null] Feature property holding a URL → clickable title.
185
214
  * @property {number} [maxPoints=2000] Decimation for render/interaction (stats use full data).
@@ -230,6 +259,18 @@
230
259
  return false;
231
260
  }
232
261
 
262
+ /**
263
+ * Whether a feature carries per-point time data (coordTimes / XYZM).
264
+ * @param {import('ol/Feature').default} feature
265
+ * @returns {boolean}
266
+ */
267
+ static featureHasTime(feature) {
268
+ const g = feature && feature.getGeometry && feature.getGeometry();
269
+ if (!g) return false;
270
+ const lines = g.getType() === 'MultiLineString' ? g.getCoordinates() : [g.getCoordinates()];
271
+ return !!extractTimes(feature, lines);
272
+ }
273
+
233
274
  // ---------- DOM ---------------------------------------------------
234
275
  _buildDom(root) {
235
276
  const o = this.options;
@@ -445,13 +486,28 @@
445
486
  const lines = geom.getType() === 'MultiLineString' ? geom.getCoordinates() : [geom.getCoordinates()];
446
487
  const dataProj = o.dataProjection || (this.getMap() && this.getMap().getView().getProjection()) || 'EPSG:3857';
447
488
 
489
+ const times = extractTimes(this._feature, lines);
490
+ this._hasTime = !!times;
491
+ const ignoreStops = o.ignoreStops !== false; // défaut : ignore les arrêts
492
+ const stopSpeed = (o.stopSpeed != null ? o.stopSpeed : 0.5); // m/s
448
493
  const pts = [];
449
- let cum = 0, prev = null;
494
+ let cum = 0, prev = null, i = -1, tAcc = 0, prevMs = null;
450
495
  for (const seg of lines) for (const c of seg) {
496
+ i++;
451
497
  const ll = proj_js.toLonLat(c, dataProj);
452
- if (prev) cum += sphere_js.getDistance(prev, ll);
498
+ let dseg = 0;
499
+ if (prev) { dseg = sphere_js.getDistance(prev, ll); cum += dseg; }
500
+ let t = null;
501
+ if (times && times[i] != null) {
502
+ const ms = times[i];
503
+ if (prevMs != null) {
504
+ const dt = (ms - prevMs) / 1000; // s sur le segment
505
+ if (dt > 0 && (!ignoreStops || (dseg / dt) >= stopSpeed)) tAcc += dt;
506
+ }
507
+ t = tAcc; prevMs = ms;
508
+ }
453
509
  prev = ll;
454
- pts.push({ x: cum, z: (c.length > 2 && isFinite(c[2])) ? c[2] : 0, coord: c });
510
+ pts.push({ x: cum, z: (c.length > 2 && isFinite(c[2])) ? c[2] : 0, coord: c, t });
455
511
  }
456
512
  if (o.smoothing > 0) this._smooth(pts, o.smoothing);
457
513
 
@@ -469,7 +525,9 @@
469
525
  const s = Math.abs(samples[k].slope || 0); if (s > maxAbs) maxAbs = s;
470
526
  }
471
527
  const distance = samples.length ? samples[samples.length - 1].x - samples[0].x : 0;
472
- return { distance, ascent, descent, min: isFinite(zmin) ? zmin : 0, max: isFinite(zmax) ? zmax : 0, maxAbsSlope: maxAbs, points: samples.length };
528
+ const ta = samples.length ? samples[0].t : null, tb = samples.length ? samples[samples.length - 1].t : null;
529
+ const duration = (ta != null && tb != null) ? (tb - ta) : null;
530
+ return { distance, duration, ascent, descent, min: isFinite(zmin) ? zmin : 0, max: isFinite(zmax) ? zmax : 0, maxAbsSlope: maxAbs, points: samples.length };
473
531
  }
474
532
  _smooth(pts, meters) {
475
533
  if (!(meters > 0) || pts.length < 3) return;
@@ -484,7 +542,7 @@
484
542
  }
485
543
  }
486
544
  _decimate(pts, maxPoints) {
487
- const copy = (p) => ({ x: p.x, z: p.z, coord: p.coord });
545
+ const copy = (p) => ({ x: p.x, z: p.z, coord: p.coord, t: p.t });
488
546
  if (!maxPoints || pts.length <= maxPoints) return pts.map(copy);
489
547
  const step = pts.length / maxPoints, out = [];
490
548
  for (let i = 0; i < maxPoints; i++) out.push(copy(pts[Math.floor(i * step)]));
@@ -523,6 +581,24 @@
523
581
  }
524
582
  _classIndex(slope, sc) { return Math.min(sc.maxIdx, Math.floor(Math.abs(slope) / sc.classSize)); }
525
583
 
584
+ // ---------- temps : format adaptatif ------------------------------
585
+ // 7 sec · 26 min · 1 h 48 min · 2 j 3 h (jours + heures normalisés)
586
+ _fmtDuration(sec) {
587
+ if (sec == null || !isFinite(sec)) return '';
588
+ const u = (this.options.labels && this.options.labels.durationUnits) || { s: 'sec', m: 'min', h: 'h', d: 'j' };
589
+ sec = Math.max(0, Math.round(sec));
590
+ if (sec < 60) return sec + ' ' + u.s;
591
+ if (sec < 3600) return Math.round(sec / 60) + ' ' + u.m;
592
+ if (sec < 86400) {
593
+ let h = Math.floor(sec / 3600), m = Math.round((sec % 3600) / 60);
594
+ if (m === 60) { h++; m = 0; }
595
+ return m ? `${h} ${u.h} ${m} ${u.m}` : `${h} ${u.h}`;
596
+ }
597
+ let d = Math.floor(sec / 86400), h = Math.round((sec % 86400) / 3600);
598
+ if (h === 24) { d++; h = 0; }
599
+ return h ? `${d} ${u.d} ${h} ${u.h}` : `${d} ${u.d}`;
600
+ }
601
+
526
602
  // ---------- entête + légende --------------------------------------
527
603
  _renderHeader() {
528
604
  const o = this.options, s = this._stats, f = this._feature;
@@ -541,6 +617,7 @@
541
617
  else if (it === 'min') { html.push(fmtElevation(s.min, o.units)); text.push(fmtElevation(s.min, o.units)); }
542
618
  else if (it === 'max') { html.push(fmtElevation(s.max, o.units)); text.push(fmtElevation(s.max, o.units)); }
543
619
  else if (it === 'minmax') { const v = `${fmtElevation(s.min, o.units)}–${fmtElevation(s.max, o.units)}`; html.push(v); text.push(v); }
620
+ else if (it === 'duration') { if (s.duration != null) { const v = this._fmtDuration(s.duration); html.push(`<span class="oep-time">${esc(o.labels.duration)} ${v}</span>`); text.push(`${o.labels.duration} ${v}`); } }
544
621
  } else if (it && it.property) {
545
622
  const val = f.get && f.get(it.property); if (val == null || val === '') return;
546
623
  const lbl = it.label ? `${it.label} ` : '';
@@ -681,7 +758,8 @@
681
758
  const raw = this._fullSamples.filter((p) => p.x >= a && p.x <= b);
682
759
  if (raw.length < 2) return;
683
760
  const off = raw[0].x; // A devient 0
684
- const cropped = raw.map((p) => ({ x: p.x - off, z: p.z, coord: p.coord, slope: p.slope }));
761
+ const tOff = raw[0].t != null ? raw[0].t : 0;
762
+ const cropped = raw.map((p) => ({ x: p.x - off, z: p.z, coord: p.coord, slope: p.slope, t: (p.t != null ? p.t - tOff : null) }));
685
763
  const coords = raw.map((p) => p.coord);
686
764
  this._samples = cropped; this._stats = this._statsOf(cropped, false); this._cropMode = true;
687
765
  this._updateZoomButtons(); this._render();
@@ -708,6 +786,7 @@
708
786
  if (it === 'distance') parts.push(fmtDistance(d.x, o.units));
709
787
  else if (it === 'elevation') parts.push(fmtElevation(d.z, o.units));
710
788
  else if (it === 'slope') parts.push(fmtSlope(d.slope || 0));
789
+ else if (it === 'time') { if (d.t != null) parts.push(this._fmtDuration(d.t)); }
711
790
  });
712
791
  return parts.join(' · ');
713
792
  }
@@ -752,7 +831,7 @@
752
831
  ElevationProfile.addTheme = (name, colors) => { THEMES[name] = colors; };
753
832
  ElevationProfile.THEMES = THEMES;
754
833
  ElevationProfile.POSITIONS = POSITIONS;
755
- ElevationProfile.version = '0.5.0';
834
+ ElevationProfile.version = '0.6.0';
756
835
 
757
836
  return ElevationProfile;
758
837
 
@@ -1,5 +1,5 @@
1
1
  /*! ol-elevation-profile | MIT License | https://github.com/lc-4918/ol-elevation-profile */
2
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("ol/control/Control.js"),require("ol/Overlay.js"),require("ol/Observable.js"),require("ol/proj.js"),require("ol/sphere.js"),require("ol/extent.js"),require("d3")):"function"==typeof define&&define.amd?define(["ol/control/Control.js","ol/Overlay.js","ol/Observable.js","ol/proj.js","ol/sphere.js","ol/extent.js","d3"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).OlElevationProfile=e(t.ol.control.Control,t.ol.Overlay,t.ol.Observable,t.ol.proj,t.ol.sphere,t.ol.extent,t.d3)}(this,function(t,e,s,o,i,l,n){"use strict";function a(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(s){if("default"!==s){var o=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,o.get?o:{enumerable:!0,get:function(){return t[s]}})}}),e.default=t,Object.freeze(e)}var r=a(n);
2
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("ol/control/Control.js"),require("ol/Overlay.js"),require("ol/Observable.js"),require("ol/proj.js"),require("ol/sphere.js"),require("ol/extent.js"),require("d3")):"function"==typeof define&&define.amd?define(["ol/control/Control.js","ol/Overlay.js","ol/Observable.js","ol/proj.js","ol/sphere.js","ol/extent.js","d3"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).OlElevationProfile=e(t.ol.control.Control,t.ol.Overlay,t.ol.Observable,t.ol.proj,t.ol.sphere,t.ol.extent,t.d3)}(this,function(t,e,s,o,i,n,l){"use strict";function a(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(s){if("default"!==s){var o=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,o.get?o:{enumerable:!0,get:function(){return t[s]}})}}),e.default=t,Object.freeze(e)}var r=a(l);
3
3
  /**
4
4
  * Synchronized elevation profile control for OpenLayers, rendered with d3.
5
5
  *
@@ -13,4 +13,4 @@
13
13
  *
14
14
  * @module ol-elevation-profile
15
15
  * @license MIT
16
- */const h={steelblue:{area:"#4682b4",line:"#3a6d96",axis:"#555",text:"#222",focus:"#e6550d"},lime:{area:"#9ccc2f",line:"#7da521",axis:"#555",text:"#222",focus:"#d62728"},purple:{area:"#9467bd",line:"#76529c",axis:"#555",text:"#222",focus:"#ff9f1c"},slate:{area:"#7c8a99",line:"#4a5560",axis:"#5a6570",text:"#26303a",focus:"#2f81f7"},graphite:{area:"#9a948c",line:"#5c574f",axis:"#5c574f",text:"#2b2823",focus:"#e07a3f"},amber:{area:"#f0a23b",line:"#c4671a",axis:"#7a5a36",text:"#3a2a16",focus:"#1f6fb2"}},p=["top","bottom","left","right","top-left","top-right","bottom-left","bottom-right"],c={immersion:"docked",position:"bottom",width:520,height:180,margins:{unit:"px",top:20,right:24,bottom:30,left:48},units:"meters",dataProjection:null,maxPoints:2e3,smoothing:0,theme:"steelblue",color:null,trackLayer:null,transparency:!1,transparencyLevel:.45,grid:!0,slope:!1,slopeClassSize:2.5,slopeColors:null,slopeSeparators:!0,slopeLegend:!0,maxClasses:8,maxLegendClasses:8,xTicks:null,yTicks:null,show:"click",collapsable:!0,collapsed:!1,followMap:!0,marker:!0,hideOnMapClick:!0,responsive:!0,mobileBreakpoint:640,zoom:!1,tooltipItems:["distance","elevation"],headerItems:["distance","ascent","descent","minmax"],titleProperty:"name",titleLink:null,labels:{distance:"Distance",elevation:"Altitude",slope:"Pente",ascent:"D+",descent:"D-",empty:"Cliquez un tracé",zoomStart:"Définir le début (A)",zoomEnd:"Définir la fin (B)",zoomAll:"Tout voir"}};function d(t,e){const s={};return Object.keys(t).forEach(e=>{s[e]=t[e]}),e&&Object.keys(e).forEach(o=>{e[o]&&"object"==typeof e[o]&&!Array.isArray(e[o])&&t[o]&&"object"==typeof t[o]?s[o]=d(t[o],e[o]):s[o]=e[o]}),s}const u=t=>String(t).replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[t])),m=t=>"string"==typeof t&&/^(https?:)?\/\/|^mailto:/i.test(t);function _(t,e,s){let o=t;if("function"==typeof o)try{o=o(e,s)}catch(t){return null}if(Array.isArray(o)&&(o=o[0]),o&&o.getStroke){const t=o.getStroke();if(t)return t.getColor()}return null}const f=(t,e)=>{if("imperial"===e){const e=t/1609.344;return e<.2?`${Math.round(3.28084*t)} ft`:`${e.toFixed(e<10?2:1)} mi`}return t<1e3?`${Math.round(t)} m`:`${(t/1e3).toFixed(t<1e4?2:1)} km`},g=(t,e)=>"imperial"===e?`${Math.round(3.28084*t)} ft`:`${Math.round(t)} m`;const y='<svg viewBox="0 0 24 24"><path d="M5 13h14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>',x='<svg viewBox="0 0 24 24"><path d="M3 19l5-7 4 4 4-6 5 9z" fill="currentColor" opacity=".85"/></svg>';class b extends t{constructor(t){const e=d(c,t||{});-1===p.indexOf(e.position)&&(e.position="bottom");const s=function(t){const e=document.createElement("div");return e.className=`ol-elevation-profile ol-unselectable ol-control oep-pos-${t.position} oep-theme-${"string"==typeof t.theme?t.theme:"custom"}`+("floating"===t.immersion?" oep-floating":""),e}(e);super({element:s,target:t&&t.target}),this.options=e,this.slopeColors=e.slopeColors||null,this._feature=null,this._fullSamples=null,this._fullStats=null,this._samples=null,this._stats=null,this._marker=null,this._collapsed=!!e.collapsed,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._onResize=()=>{this._feature&&!this._collapsed&&this._render()},this._buildDom(s),s.style.display="none"}static featureHasZ(t){const e=t&&t.getGeometry&&t.getGeometry();if(!e)return!1;const s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()];for(const t of s)for(const e of t)if(e.length>2&&isFinite(e[2]))return!0;return!1}_buildDom(t){const e=this.options;this._applyTheme(),this._applyTransparency();const s=document.createElement("div");s.className="oep-header",this._titleEl=document.createElement("span"),this._titleEl.className="oep-title",this._titleEl.textContent=e.labels.empty,this._statsEl=document.createElement("span"),this._statsEl.className="oep-stats",s.appendChild(this._titleEl),s.appendChild(this._statsEl),this._toolbar=document.createElement("span"),this._toolbar.className="oep-toolbar";const o=(t,e,s,o)=>{const i=document.createElement("button");return i.type="button",i.className=`oep-tbtn ${t}`,i.innerHTML=e,i.title=s,i.setAttribute("aria-label",s),i.addEventListener("click",o),this._toolbar.appendChild(i),i};this._btnA=o("oep-a",'<svg viewBox="0 0 24 24"><path d="M7 5v14M11 12h8m0 0-3-3m3 3-3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomStart,()=>this._arm("A")),this._btnB=o("oep-b",'<svg viewBox="0 0 24 24"><path d="M17 5v14M13 12H5m0 0 3-3m-3 3 3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomEnd,()=>this._arm("B")),this._btnAll=o("oep-all",'<svg viewBox="0 0 24 24"><path d="M4 12h16M4 12l4-4M4 12l4 4M20 12l-4-4M20 12l-4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomAll,()=>this._exitZoom()),s.appendChild(this._toolbar);const i=document.createElement("button");i.className="oep-toggle",i.type="button",i.setAttribute("aria-label","Réduire ou agrandir le profil"),i.innerHTML=this._collapsed?x:y,i.addEventListener("click",()=>this.toggleCollapsed()),s.appendChild(i),this._toggleBtn=i,this._legendEl=document.createElement("div"),this._legendEl.className="oep-legend",this._legendEl.style.display="none",this._body=document.createElement("div"),this._body.className="oep-body",t.appendChild(s),t.appendChild(this._legendEl),t.appendChild(this._body),this._applyCollapsable(),this._updateZoomButtons(),this._collapsed&&t.classList.add("oep-collapsed")}_resolveColor(){const t=this.options;return t.color?"auto"===t.color?this._featureColor():t.color:null}_featureColor(){const t=this._feature;if(!t)return null;const e=this.getMap()?this.getMap().getView().getResolution():1;let s=_(t.getStyle&&t.getStyle(),t,e);return!s&&this.options.trackLayer&&(s=_(this.options.trackLayer.getStyle&&this.options.trackLayer.getStyle(),t,e)),function(t){if(null==t)return null;if("string"==typeof t)return t;if(Array.isArray(t)){const e=t.length>3?t[3]:1;return`rgba(${0|t[0]},${0|t[1]},${0|t[2]},${e})`}return null}(s)}_applyTheme(){const t=this.options,e="object"==typeof t.theme?t.theme:h[t.theme]||h.steelblue;this.themeColors=e;const s=this.element,o=this._resolveColor(),i=o||e.area;let l=e.line;if(o)try{l=String(r.color(o).darker(.7))}catch(t){l=o}s.style.setProperty("--oep-area",i),s.style.setProperty("--oep-line",l),s.style.setProperty("--oep-axis",e.axis),s.style.setProperty("--oep-text",e.text),s.style.setProperty("--oep-focus",e.focus)}_applyTransparency(){const t=this.options.transparency;let e;e=!1===t||null==t?1:!0===t?this.options.transparencyLevel:Math.max(0,Math.min(1,+t)),this.element.style.setProperty("--oep-bg",`rgba(255,255,255,${e})`),this.element.classList.toggle("oep-transparent",e<1)}_applyCollapsable(){const t=this.options.collapsable;this._toggleBtn.style.display=t?"":"none",!t&&this._collapsed&&this.toggleCollapsed(!1)}toggleCollapsed(t){this._collapsed="boolean"==typeof t?t:!this._collapsed,this.element.classList.toggle("oep-collapsed",this._collapsed),this._toggleBtn.innerHTML=this._collapsed?x:y,!this._collapsed&&this._feature&&this._render(),this._adjustAttribution()}_availWidth(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement();return e&&e.clientWidth||("undefined"!=typeof window?window.innerWidth:1024)}_isMobile(){return this.options.responsive&&"undefined"!=typeof window&&window.innerWidth<=(this.options.mobileBreakpoint||640)}_applyPlacement(){const t=this._isMobile();let e=this.options.position;return t&&(e=/top/.test(e)?"top":"bottom"),this.element.className=this.element.className.replace(/oep-pos-\S+/,`oep-pos-${e}`),this.element.classList.toggle("oep-mobile",t),t}_adjustAttribution(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement(),s=e&&e.querySelector&&e.querySelector(".ol-attribution");if(!s)return;if(s.style.bottom="",s.style.right="","none"===this.element.style.display||this._collapsed)return;const o=e.getBoundingClientRect(),i=this.element.getBoundingClientRect();if(!i.height)return;const l=o.bottom-i.bottom,n=o.right-i.right;l<i.height&&n<24&&(s.style.right=`${Math.max(0,Math.round(n))}px`,s.style.bottom=`${Math.round(i.height+2*l)}px`)}setMap(t){const o=this.getMap();if(super.setMap(t),this._mapKeys&&(this._mapKeys.forEach(s.unByKey),this._mapKeys=null),o&&"undefined"!=typeof window&&window.removeEventListener("resize",this._onResize),this._marker&&!t&&this._marker.setPosition(void 0),!t)return;const i=this.options;if(i.dataProjection||(i.dataProjection=t.getView().getProjection()),i.marker&&!this._marker){const s=document.createElement("div");s.className="oep-marker",this._marker=new e({element:s,positioning:"center-center",stopEvent:!1}),t.addOverlay(this._marker)}"undefined"!=typeof window&&window.addEventListener("resize",this._onResize);const l=e=>t.forEachFeatureAtPixel(e,t=>{const e=t.getGeometry();return e&&/LineString/.test(e.getType())?t:void 0});this._mapKeys=[],this._mapKeys.push(t.on("mouseover"===i.show?"pointermove":"click",t=>{const e=l(t.pixel);e&&e!==this._feature&&this.setFeature(e)})),i.hideOnMapClick&&this._mapKeys.push(t.on("click",t=>{!l(t.pixel)&&this._feature&&this.clear()})),i.followMap&&this._mapKeys.push(t.on("pointermove",e=>{if(!this._feature||this._collapsed)return;const s=this._feature.getGeometry().getClosestPoint(e.coordinate),o=t.getPixelFromCoordinate(s);o&&(Math.hypot(o[0]-e.pixel[0],o[1]-e.pixel[1])<14?this._focusByCoord(s):this._clearFocus())})),this._mapKeys.push(t.on("moveend",()=>{this._cropMode&&this._fitRes&&t.getView().getResolution()>1.25*this._fitRes&&this._exitZoom()})),this._mapKeys.push(t.on("change:size",this._onResize))}setFeature(t){return this._feature=t||null,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,t?(this.element.style.display="",this._compute(),this._updateZoomButtons(),this._collapsed||this._render(),this):(this._fullSamples=this._samples=null,this._clear(),this)}clear(){return this.setFeature(null)}getStats(){return this._stats}setTheme(t){return this.options.theme=t,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setColor(t){return this.options.color=t||null,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setOptions(t){return this.options=d(this.options,t||{}),t&&"slopeColors"in t&&(this.slopeColors=t.slopeColors||null),t&&(t.theme||"color"in t)&&this._applyTheme(),t&&("transparency"in t||"transparencyLevel"in t)&&this._applyTransparency(),t&&"collapsable"in t&&this._applyCollapsable(),t&&"zoom"in t&&this._updateZoomButtons(),t&&void 0!==t.width&&"number"==typeof t.width&&(this.options.width=t.width),this._feature&&(this._compute(),this._updateZoomButtons(),this._collapsed||this._render()),this}_compute(){const t=this.options,e=this._feature.getGeometry(),s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()],l=t.dataProjection||this.getMap()&&this.getMap().getView().getProjection()||"EPSG:3857",n=[];let a=0,r=null;for(const t of s)for(const e of t){const t=o.toLonLat(e,l);r&&(a+=i.getDistance(r,t)),r=t,n.push({x:a,z:e.length>2&&isFinite(e[2])?e[2]:0,coord:e})}t.smoothing>0&&this._smooth(n,t.smoothing);const h=this._decimate(n,t.maxPoints);this._addSlope(h),this._fullSamples=h,this._fullStats=this._statsOf(h,!0),this._samples=h,this._stats=this._fullStats}_statsOf(t,e){let s=0,o=0,i=1/0,l=-1/0,n=0;for(let e=0;e<t.length;e++){const a=t[e].z;if(a<i&&(i=a),a>l&&(l=a),e>0){const i=a-t[e-1].z;i>0?s+=i:o-=i}const r=Math.abs(t[e].slope||0);r>n&&(n=r)}return{distance:t.length?t[t.length-1].x-t[0].x:0,ascent:s,descent:o,min:isFinite(i)?i:0,max:isFinite(l)?l:0,maxAbsSlope:n,points:t.length}}_smooth(t,e){if(!(e>0)||t.length<3)return;const s=e/2,o=t.map(t=>t.z);let i=0,l=0,n=0;for(let e=0;e<t.length;e++){const a=t[e].x;for(;i<t.length&&t[i].x<a-s;)n-=o[i],i++;for(;l<t.length&&t[l].x<=a+s;)n+=o[l],l++;t[e].z=l>i?n/(l-i):o[e]}}_decimate(t,e){const s=t=>({x:t.x,z:t.z,coord:t.coord});if(!e||t.length<=e)return t.map(s);const o=t.length/e,i=[];for(let l=0;l<e;l++)i.push(s(t[Math.floor(l*o)]));return i.push(s(t[t.length-1])),i}_addSlope(t){for(let e=1;e<t.length;e++){const s=t[e].x-t[e-1].x;t[e].slope=s>0?(t[e].z-t[e-1].z)/s*100:0}t.length&&(t[0].slope=t.length>1?t[1].slope:0)}_slopeScale(){const t=this.options.slopeClassSize||2.5,e=this.options.maxClasses||8,s=Math.max(1,Math.floor((this._stats.maxAbsSlope||0)/t)),o=Math.min(s,e-1),i=s>o;let l;if(this.slopeColors&&this.slopeColors.length){const t=r.interpolateRgbBasis(this.slopeColors);l=e=>t(o?e/o:0)}else{const t=r.scaleLinear().domain([0,.16,.42,.68,1]).range(["#2166ac","#27a35a","#ffe000","#f4791f","#d7191c"]).interpolate(r.interpolateRgb).clamp(!0);l=e=>String(t(o?e/o:0))}return{classSize:t,maxIdx:o,capped:i,colorByIndex:l}}_classIndex(t,e){return Math.min(e.maxIdx,Math.floor(Math.abs(t)/e.classSize))}_renderHeader(){const t=this.options,e=this._stats,s=this._feature,o=s.get&&s.get(t.titleProperty)||"Profil",i=t.titleLink&&s.get&&s.get(t.titleLink);m(i)?this._titleEl.innerHTML=`<a href="${u(i)}" target="_blank" rel="noopener">${u(o)}</a>`:this._titleEl.textContent=o,this._titleEl.setAttribute("title",o);const l=[],n=[];if(t.headerItems.forEach(o=>{if("string"==typeof o){if("distance"===o)l.push(`<b>${f(e.distance,t.units)}</b>`),n.push(f(e.distance,t.units));else if("ascent"===o)l.push(`<span class="oep-up">${t.labels.ascent} ${g(e.ascent,t.units)}</span>`),n.push(`${t.labels.ascent} ${g(e.ascent,t.units)}`);else if("descent"===o)l.push(`<span class="oep-down">${t.labels.descent} ${g(e.descent,t.units)}</span>`),n.push(`${t.labels.descent} ${g(e.descent,t.units)}`);else if("min"===o)l.push(g(e.min,t.units)),n.push(g(e.min,t.units));else if("max"===o)l.push(g(e.max,t.units)),n.push(g(e.max,t.units));else if("minmax"===o){const s=`${g(e.min,t.units)}–${g(e.max,t.units)}`;l.push(s),n.push(s)}}else if(o&&o.property){const t=s.get&&s.get(o.property);if(null==t||""===t)return;const e=o.label?`${o.label} `:"";o.asLink&&m(t)?(l.push(`${e}<a href="${u(t)}" target="_blank" rel="noopener">${u(o.linkText||o.label||t)}</a>`),n.push(`${o.label||""} ${t}`)):(l.push(u(e+t)),n.push(e+t))}}),this._statsEl.innerHTML=l.join(" · "),this._statsEl.setAttribute("title",n.join(" · ")),this._legendEl.innerHTML="",t.slope&&t.slopeLegend){const t=this._slopeScale();for(let e=0;e<=t.maxIdx;e++){const s=t.colorByIndex(e),o=t.capped&&e===t.maxIdx?`≥ ${e*t.classSize} %`:`${e*t.classSize}–${(e+1)*t.classSize} %`,i=document.createElement("span");i.className="oep-leg-it",i.innerHTML=`<i class="oep-sw" style="background:${s}"></i>${o}`,this._legendEl.appendChild(i)}this._legendEl.style.display=""}else this._legendEl.style.display="none"}_render(){const t=this.options,e=this._stats,s=this._samples;if(!s||!s.length)return;this._applyTheme();const o=this._applyPlacement();this._renderHeader();const i=t.margins,l=i.unit||"px",n=t=>"px"===l?t:t*(parseFloat(getComputedStyle(this.element).fontSize)||16),a=this._availWidth(),h="auto"===t.width||"100%"===t.width||"full"===t.width?a:Math.min("number"==typeof t.width?t.width:parseFloat(t.width)||a,a);this.element.style.width=o?"":`${h}px`;const p=this.element.clientWidth||(o?a:h),c=Math.max(220,p-16),d="number"==typeof t.height?t.height:180,u=n(i.top),m=n(i.right),_=n(i.bottom),f=n(i.left),g=Math.max(10,c-f-m),y=Math.max(10,d-u-_),x=null!=t.xTicks?t.xTicks:Math.max(2,Math.round(g/80)),b=null!=t.yTicks?t.yTicks:Math.max(2,Math.round(y/40));this._body.innerHTML="";const M=r.select(this._body).append("svg").attr("class","oep-svg").attr("width",c).attr("height",d).attr("viewBox",`0 0 ${c} ${d}`),k=M.append("g").attr("transform",`translate(${f},${u})`),v=r.scaleLinear().domain([0,e.distance]).range([0,g]),z=.1*(e.max-e.min)||10,E=r.scaleLinear().domain([e.min-z,e.max+z]).range([y,0]).nice();this._x=v,this._y=E,this._dims={innerW:g,innerH:y},t.grid&&k.append("g").attr("class","oep-grid").call(r.axisLeft(E).ticks(b).tickSize(-g).tickFormat(""));const C="imperial"===t.units?1609.344:1e3;const w=r.area().x(t=>v(t.x)).y0(y).y1(t=>E(t.z));if(t.slope){const e=this._slopeScale(),o=[];let i=1;for(;i<s.length;){const t=this._classIndex(s[i].slope,e);let l=i;for(;l+1<s.length&&this._classIndex(s[l+1].slope,e)===t;)l++;k.append("path").datum(s.slice(i-1,l+1)).attr("class","oep-area-slope").attr("fill",e.colorByIndex(t)).attr("d",w),i>1&&o.push(s[i-1]),i=l+1}t.slopeSeparators&&o.forEach(t=>{k.append("line").attr("class","oep-slope-sep").attr("x1",v(t.x)).attr("x2",v(t.x)).attr("y1",E(t.z)).attr("y2",y)})}else k.append("path").datum(s).attr("class","oep-area").attr("d",w);k.append("path").datum(s).attr("class","oep-line").attr("d",r.line().x(t=>v(t.x)).y(t=>E(t.z))),k.append("g").attr("class","oep-axis oep-axis-x").attr("transform",`translate(0,${y})`).call(r.axisBottom(v).ticks(x).tickFormat(t=>(t/C).toFixed(t/C<10?1:0))),k.append("text").attr("class","oep-axis-label").attr("x",g).attr("y",y+_-4).attr("text-anchor","end").text((t=>"imperial"===t?"mi":"km")(t.units)),k.append("g").attr("class","oep-axis oep-axis-y").call(r.axisLeft(E).ticks(b).tickFormat(e=>"imperial"===t.units?Math.round(3.28084*e):e)),t.zoom&&!this._cropMode&&[["A",this._zoomA],["B",this._zoomB]].forEach(([t,e])=>{if(null==e)return;const s=v(e);k.append("line").attr("class","oep-ab-line").attr("x1",s).attr("x2",s).attr("y1",0).attr("y2",y),k.append("text").attr("class","oep-ab-label").attr("x",s).attr("y",-6).attr("text-anchor","middle").text(t)});const S=k.append("g").attr("class","oep-focus").style("display","none");S.append("line").attr("class","oep-focus-line").attr("y1",0).attr("y2",y),S.append("circle").attr("class","oep-focus-dot").attr("r",4);const $=S.append("g").attr("class","oep-focus-label");$.append("rect").attr("class","oep-focus-bg"),$.append("text").attr("class","oep-focus-txt"),this._focus=S;const B=r.bisector(t=>t.x).left,L=t=>{const s=r.pointer(t,k.node())[0];return Math.max(0,Math.min(e.distance,v.invert(s)))},T=M.append("rect").attr("class","oep-overlay").attr("x",f).attr("y",u).attr("width",g).attr("height",y);T.on("mousemove",t=>{const e=L(t),o=B(s,e,1),i=s[o-1],l=s[o]||i,n=e-i.x>l.x-e?l:i;this._setFocus(n),this._marker&&this._marker.setPosition(n.coord)}).on("mouseout",()=>this._clearFocus()),T.on("click",t=>{if(!this._armed||this._cropMode)return;const e=L(t);"A"===this._armed?this._zoomA=e:this._zoomB=e,this._armed=null,this._updateZoomButtons(),null!=this._zoomA&&null!=this._zoomB?this._applyZoom():this._render()}),this._armed&&T.style("cursor","col-resize"),this._adjustAttribution()}_arm(t){this._armed=this._armed===t?null:t,this._updateZoomButtons(),this._focus&&this._render()}_updateZoomButtons(){const t=!!this.options.zoom&&!!this._feature;this._toolbar.style.display=t?"":"none";const e=this._cropMode;this._btnA.style.display=t&&!e?"":"none",this._btnB.style.display=t&&!e?"":"none",this._btnAll.style.display=t&&e?"":"none",this._btnA.classList.toggle("armed","A"===this._armed),this._btnB.classList.toggle("armed","B"===this._armed)}_applyZoom(){const t=Math.min(this._zoomA,this._zoomB),e=Math.max(this._zoomA,this._zoomB),s=this._fullSamples.filter(s=>s.x>=t&&s.x<=e);if(s.length<2)return;const o=s[0].x,i=s.map(t=>({x:t.x-o,z:t.z,coord:t.coord,slope:t.slope})),n=s.map(t=>t.coord);this._samples=i,this._stats=this._statsOf(i,!1),this._cropMode=!0,this._updateZoomButtons(),this._render();const a=this.getMap();if(a){const t=l.boundingExtent(n),e=a.getView();try{this._fitRes=e.getResolutionForExtent(t,a.getSize())}catch(t){this._fitRes=null}e.fit(t,{padding:[40,40,40,40],duration:400})}}_exitZoom(){this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._samples=this._fullSamples,this._stats=this._fullStats,this._updateZoomButtons(),this._collapsed||this._render();const t=this.getMap();t&&this._feature&&t.getView().fit(this._feature.getGeometry().getExtent(),{padding:[40,40,40,40],duration:400})}_tooltipText(t){const e=this.options,s=[];return e.tooltipItems.forEach(o=>{var i;"distance"===o?s.push(f(t.x,e.units)):"elevation"===o?s.push(g(t.z,e.units)):"slope"===o&&s.push(`${(i=t.slope||0)>=0?"+":""}${i.toFixed(1)} %`)}),s.join(" · ")}_setFocus(t){if(!this._focus)return;const e=this._x,s=this._y;this._focus.style("display",null),this._focus.select(".oep-focus-line").attr("x1",e(t.x)).attr("x2",e(t.x)),this._focus.select(".oep-focus-dot").attr("cx",e(t.x)).attr("cy",s(t.z));let o=s(t.z)-16;o<12&&(o=s(t.z)+22);const i=this._focus.select(".oep-focus-txt").attr("x",e(t.x)).attr("y",o).text(this._tooltipText(t)),l=i.node().getBBox();this._focus.select(".oep-focus-bg").attr("x",l.x-4).attr("y",l.y-2).attr("width",l.width+8).attr("height",l.height+4),e(t.x)+l.width/2>this._dims.innerW?i.attr("text-anchor","end"):e(t.x)-l.width/2<0?i.attr("text-anchor","start"):i.attr("text-anchor","middle")}_focusByCoord(t){const e=this._samples;if(!e)return;let s=null,o=1/0;for(const i of e){const e=i.coord[0]-t[0],l=i.coord[1]-t[1],n=e*e+l*l;n<o&&(o=n,s=i)}s&&(this._setFocus(s),this._marker&&this._marker.setPosition(s.coord))}_clearFocus(){this._focus&&this._focus.style("display","none"),this._marker&&this._marker.setPosition(void 0)}_clear(){this._body.innerHTML="",this._legendEl.innerHTML="",this._legendEl.style.display="none",this._titleEl.textContent=this.options.labels.empty,this._titleEl.removeAttribute("title"),this._statsEl.innerHTML="",this._statsEl.removeAttribute("title"),this._cropMode=!1,this._updateZoomButtons(),this._clearFocus(),this.element.style.display="none",this._adjustAttribution()}}return b.addTheme=(t,e)=>{h[t]=e},b.THEMES=h,b.POSITIONS=p,b.version="0.5.0",b});
16
+ */const h={steelblue:{area:"#4682b4",line:"#3a6d96",axis:"#555",text:"#222",focus:"#e6550d"},lime:{area:"#9ccc2f",line:"#7da521",axis:"#555",text:"#222",focus:"#d62728"},purple:{area:"#9467bd",line:"#76529c",axis:"#555",text:"#222",focus:"#ff9f1c"},slate:{area:"#7c8a99",line:"#4a5560",axis:"#5a6570",text:"#26303a",focus:"#2f81f7"},graphite:{area:"#9a948c",line:"#5c574f",axis:"#5c574f",text:"#2b2823",focus:"#e07a3f"},amber:{area:"#f0a23b",line:"#c4671a",axis:"#7a5a36",text:"#3a2a16",focus:"#1f6fb2"}},p=["top","bottom","left","right","top-left","top-right","bottom-left","bottom-right"],c={immersion:"docked",position:"bottom",width:520,height:180,margins:{unit:"px",top:20,right:24,bottom:30,left:48},units:"meters",dataProjection:null,maxPoints:2e3,smoothing:0,theme:"steelblue",color:null,trackLayer:null,transparency:!1,transparencyLevel:.45,grid:!0,slope:!1,slopeClassSize:2.5,slopeColors:null,slopeSeparators:!0,slopeLegend:!0,maxClasses:8,xTicks:null,yTicks:null,show:"click",collapsable:!0,collapsed:!1,followMap:!0,marker:!0,hideOnMapClick:!0,responsive:!0,mobileBreakpoint:640,zoom:!1,ignoreStops:!0,stopSpeed:.5,tooltipItems:["distance","elevation"],headerItems:["distance","ascent","descent","minmax"],titleProperty:"name",titleLink:null,labels:{distance:"Distance",elevation:"Altitude",slope:"Pente",ascent:"D+",descent:"D-",empty:"Cliquez un tracé",time:"Temps",duration:"Durée",durationUnits:{s:"sec",m:"min",h:"h",d:"j"},zoomStart:"Définir le début (A)",zoomEnd:"Définir la fin (B)",zoomAll:"Tout voir"}};function u(t,e){const s={};return Object.keys(t).forEach(e=>{s[e]=t[e]}),e&&Object.keys(e).forEach(o=>{e[o]&&"object"==typeof e[o]&&!Array.isArray(e[o])&&t[o]&&"object"==typeof t[o]?s[o]=u(t[o],e[o]):s[o]=e[o]}),s}const d=t=>String(t).replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[t])),m=t=>"string"==typeof t&&/^(https?:)?\/\/|^mailto:/i.test(t);function _(t,e){const s=t&&t.getProperties?t.getProperties():{};let o=s.coordTimes;null==o&&s.coordinateProperties&&(o=s.coordinateProperties.times||s.coordinateProperties.coordTimes);let i=null;if(Array.isArray(o)&&(i=Array.isArray(o[0])?o.reduce((t,e)=>t.concat(e),[]):o.slice()),!i&&e){const t=[];let s=!1;for(const o of e)for(const e of o){const o=e.length>3?e[3]:null;t.push(o),null!=o&&isFinite(o)&&(s=!0)}i=s?t:null}if(!i)return null;let n=0;const l=i.map(t=>{if(null==t||""===t)return null;const e="number"==typeof t?t>1e12?t:1e3*t:Date.parse(t);return isFinite(e)?(n++,e):null});return n>=2?l:null}function f(t,e,s){let o=t;if("function"==typeof o)try{o=o(e,s)}catch(t){return null}if(Array.isArray(o)&&(o=o[0]),o&&o.getStroke){const t=o.getStroke();if(t)return t.getColor()}return null}const g=(t,e)=>{if("imperial"===e){const e=t/1609.344;return e<.2?`${Math.round(3.28084*t)} ft`:`${e.toFixed(e<10?2:1)} mi`}return t<1e3?`${Math.round(t)} m`:`${(t/1e3).toFixed(t<1e4?2:1)} km`},y=(t,e)=>"imperial"===e?`${Math.round(3.28084*t)} ft`:`${Math.round(t)} m`;const x='<svg viewBox="0 0 24 24"><path d="M5 13h14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>',b='<svg viewBox="0 0 24 24"><path d="M3 19l5-7 4 4 4-6 5 9z" fill="currentColor" opacity=".85"/></svg>';class M extends t{constructor(t){const e=u(c,t||{});-1===p.indexOf(e.position)&&(e.position="bottom");const s=function(t){const e=document.createElement("div");return e.className=`ol-elevation-profile ol-unselectable ol-control oep-pos-${t.position} oep-theme-${"string"==typeof t.theme?t.theme:"custom"}`+("floating"===t.immersion?" oep-floating":""),e}(e);super({element:s,target:t&&t.target}),this.options=e,this.slopeColors=e.slopeColors||null,this._feature=null,this._fullSamples=null,this._fullStats=null,this._samples=null,this._stats=null,this._marker=null,this._collapsed=!!e.collapsed,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._onResize=()=>{this._feature&&!this._collapsed&&this._render()},this._buildDom(s),s.style.display="none"}static featureHasZ(t){const e=t&&t.getGeometry&&t.getGeometry();if(!e)return!1;const s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()];for(const t of s)for(const e of t)if(e.length>2&&isFinite(e[2]))return!0;return!1}static featureHasTime(t){const e=t&&t.getGeometry&&t.getGeometry();if(!e)return!1;return!!_(t,"MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()])}_buildDom(t){const e=this.options;this._applyTheme(),this._applyTransparency();const s=document.createElement("div");s.className="oep-header",this._titleEl=document.createElement("span"),this._titleEl.className="oep-title",this._titleEl.textContent=e.labels.empty,this._statsEl=document.createElement("span"),this._statsEl.className="oep-stats",s.appendChild(this._titleEl),s.appendChild(this._statsEl),this._toolbar=document.createElement("span"),this._toolbar.className="oep-toolbar";const o=(t,e,s,o)=>{const i=document.createElement("button");return i.type="button",i.className=`oep-tbtn ${t}`,i.innerHTML=e,i.title=s,i.setAttribute("aria-label",s),i.addEventListener("click",o),this._toolbar.appendChild(i),i};this._btnA=o("oep-a",'<svg viewBox="0 0 24 24"><path d="M7 5v14M11 12h8m0 0-3-3m3 3-3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomStart,()=>this._arm("A")),this._btnB=o("oep-b",'<svg viewBox="0 0 24 24"><path d="M17 5v14M13 12H5m0 0 3-3m-3 3 3 3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomEnd,()=>this._arm("B")),this._btnAll=o("oep-all",'<svg viewBox="0 0 24 24"><path d="M4 12h16M4 12l4-4M4 12l4 4M20 12l-4-4M20 12l-4 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.labels.zoomAll,()=>this._exitZoom()),s.appendChild(this._toolbar);const i=document.createElement("button");i.className="oep-toggle",i.type="button",i.setAttribute("aria-label","Réduire ou agrandir le profil"),i.innerHTML=this._collapsed?b:x,i.addEventListener("click",()=>this.toggleCollapsed()),s.appendChild(i),this._toggleBtn=i,this._legendEl=document.createElement("div"),this._legendEl.className="oep-legend",this._legendEl.style.display="none",this._body=document.createElement("div"),this._body.className="oep-body",t.appendChild(s),t.appendChild(this._legendEl),t.appendChild(this._body),this._applyCollapsable(),this._updateZoomButtons(),this._collapsed&&t.classList.add("oep-collapsed")}_resolveColor(){const t=this.options;return t.color?"auto"===t.color?this._featureColor():t.color:null}_featureColor(){const t=this._feature;if(!t)return null;const e=this.getMap()?this.getMap().getView().getResolution():1;let s=f(t.getStyle&&t.getStyle(),t,e);return!s&&this.options.trackLayer&&(s=f(this.options.trackLayer.getStyle&&this.options.trackLayer.getStyle(),t,e)),function(t){if(null==t)return null;if("string"==typeof t)return t;if(Array.isArray(t)){const e=t.length>3?t[3]:1;return`rgba(${0|t[0]},${0|t[1]},${0|t[2]},${e})`}return null}(s)}_applyTheme(){const t=this.options,e="object"==typeof t.theme?t.theme:h[t.theme]||h.steelblue;this.themeColors=e;const s=this.element,o=this._resolveColor(),i=o||e.area;let n=e.line;if(o)try{n=String(r.color(o).darker(.7))}catch(t){n=o}s.style.setProperty("--oep-area",i),s.style.setProperty("--oep-line",n),s.style.setProperty("--oep-axis",e.axis),s.style.setProperty("--oep-text",e.text),s.style.setProperty("--oep-focus",e.focus)}_applyTransparency(){const t=this.options.transparency;let e;e=!1===t||null==t?1:!0===t?this.options.transparencyLevel:Math.max(0,Math.min(1,+t)),this.element.style.setProperty("--oep-bg",`rgba(255,255,255,${e})`),this.element.classList.toggle("oep-transparent",e<1)}_applyCollapsable(){const t=this.options.collapsable;this._toggleBtn.style.display=t?"":"none",!t&&this._collapsed&&this.toggleCollapsed(!1)}toggleCollapsed(t){this._collapsed="boolean"==typeof t?t:!this._collapsed,this.element.classList.toggle("oep-collapsed",this._collapsed),this._toggleBtn.innerHTML=this._collapsed?b:x,!this._collapsed&&this._feature&&this._render(),this._adjustAttribution()}_availWidth(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement();return e&&e.clientWidth||("undefined"!=typeof window?window.innerWidth:1024)}_isMobile(){return this.options.responsive&&"undefined"!=typeof window&&window.innerWidth<=(this.options.mobileBreakpoint||640)}_applyPlacement(){const t=this._isMobile();let e=this.options.position;return t&&(e=/top/.test(e)?"top":"bottom"),this.element.className=this.element.className.replace(/oep-pos-\S+/,`oep-pos-${e}`),this.element.classList.toggle("oep-mobile",t),t}_adjustAttribution(){const t=this.getMap(),e=t&&t.getTargetElement&&t.getTargetElement(),s=e&&e.querySelector&&e.querySelector(".ol-attribution");if(!s)return;if(s.style.bottom="",s.style.right="","none"===this.element.style.display||this._collapsed)return;const o=e.getBoundingClientRect(),i=this.element.getBoundingClientRect();if(!i.height)return;const n=o.bottom-i.bottom,l=o.right-i.right;n<i.height&&l<24&&(s.style.right=`${Math.max(0,Math.round(l))}px`,s.style.bottom=`${Math.round(i.height+2*n)}px`)}setMap(t){const o=this.getMap();if(super.setMap(t),this._mapKeys&&(this._mapKeys.forEach(s.unByKey),this._mapKeys=null),o&&"undefined"!=typeof window&&window.removeEventListener("resize",this._onResize),this._marker&&!t&&this._marker.setPosition(void 0),!t)return;const i=this.options;if(i.dataProjection||(i.dataProjection=t.getView().getProjection()),i.marker&&!this._marker){const s=document.createElement("div");s.className="oep-marker",this._marker=new e({element:s,positioning:"center-center",stopEvent:!1}),t.addOverlay(this._marker)}"undefined"!=typeof window&&window.addEventListener("resize",this._onResize);const n=e=>t.forEachFeatureAtPixel(e,t=>{const e=t.getGeometry();return e&&/LineString/.test(e.getType())?t:void 0});this._mapKeys=[],this._mapKeys.push(t.on("mouseover"===i.show?"pointermove":"click",t=>{const e=n(t.pixel);e&&e!==this._feature&&this.setFeature(e)})),i.hideOnMapClick&&this._mapKeys.push(t.on("click",t=>{!n(t.pixel)&&this._feature&&this.clear()})),i.followMap&&this._mapKeys.push(t.on("pointermove",e=>{if(!this._feature||this._collapsed)return;const s=this._feature.getGeometry().getClosestPoint(e.coordinate),o=t.getPixelFromCoordinate(s);o&&(Math.hypot(o[0]-e.pixel[0],o[1]-e.pixel[1])<14?this._focusByCoord(s):this._clearFocus())})),this._mapKeys.push(t.on("moveend",()=>{this._cropMode&&this._fitRes&&t.getView().getResolution()>1.25*this._fitRes&&this._exitZoom()})),this._mapKeys.push(t.on("change:size",this._onResize))}setFeature(t){return this._feature=t||null,this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,t?(this.element.style.display="",this._compute(),this._updateZoomButtons(),this._collapsed||this._render(),this):(this._fullSamples=this._samples=null,this._clear(),this)}clear(){return this.setFeature(null)}getStats(){return this._stats}setTheme(t){return this.options.theme=t,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setColor(t){return this.options.color=t||null,this._applyTheme(),this._feature&&!this._collapsed&&this._render(),this}setOptions(t){return this.options=u(this.options,t||{}),t&&"slopeColors"in t&&(this.slopeColors=t.slopeColors||null),t&&(t.theme||"color"in t)&&this._applyTheme(),t&&("transparency"in t||"transparencyLevel"in t)&&this._applyTransparency(),t&&"collapsable"in t&&this._applyCollapsable(),t&&"zoom"in t&&this._updateZoomButtons(),t&&void 0!==t.width&&"number"==typeof t.width&&(this.options.width=t.width),this._feature&&(this._compute(),this._updateZoomButtons(),this._collapsed||this._render()),this}_compute(){const t=this.options,e=this._feature.getGeometry(),s="MultiLineString"===e.getType()?e.getCoordinates():[e.getCoordinates()],n=t.dataProjection||this.getMap()&&this.getMap().getView().getProjection()||"EPSG:3857",l=_(this._feature,s);this._hasTime=!!l;const a=!1!==t.ignoreStops,r=null!=t.stopSpeed?t.stopSpeed:.5,h=[];let p=0,c=null,u=-1,d=0,m=null;for(const t of s)for(const e of t){u++;const t=o.toLonLat(e,n);let s=0;c&&(s=i.getDistance(c,t),p+=s);let _=null;if(l&&null!=l[u]){const t=l[u];if(null!=m){const e=(t-m)/1e3;e>0&&(!a||s/e>=r)&&(d+=e)}_=d,m=t}c=t,h.push({x:p,z:e.length>2&&isFinite(e[2])?e[2]:0,coord:e,t:_})}t.smoothing>0&&this._smooth(h,t.smoothing);const f=this._decimate(h,t.maxPoints);this._addSlope(f),this._fullSamples=f,this._fullStats=this._statsOf(f,!0),this._samples=f,this._stats=this._fullStats}_statsOf(t,e){let s=0,o=0,i=1/0,n=-1/0,l=0;for(let e=0;e<t.length;e++){const a=t[e].z;if(a<i&&(i=a),a>n&&(n=a),e>0){const i=a-t[e-1].z;i>0?s+=i:o-=i}const r=Math.abs(t[e].slope||0);r>l&&(l=r)}const a=t.length?t[t.length-1].x-t[0].x:0,r=t.length?t[0].t:null,h=t.length?t[t.length-1].t:null;return{distance:a,duration:null!=r&&null!=h?h-r:null,ascent:s,descent:o,min:isFinite(i)?i:0,max:isFinite(n)?n:0,maxAbsSlope:l,points:t.length}}_smooth(t,e){if(!(e>0)||t.length<3)return;const s=e/2,o=t.map(t=>t.z);let i=0,n=0,l=0;for(let e=0;e<t.length;e++){const a=t[e].x;for(;i<t.length&&t[i].x<a-s;)l-=o[i],i++;for(;n<t.length&&t[n].x<=a+s;)l+=o[n],n++;t[e].z=n>i?l/(n-i):o[e]}}_decimate(t,e){const s=t=>({x:t.x,z:t.z,coord:t.coord,t:t.t});if(!e||t.length<=e)return t.map(s);const o=t.length/e,i=[];for(let n=0;n<e;n++)i.push(s(t[Math.floor(n*o)]));return i.push(s(t[t.length-1])),i}_addSlope(t){for(let e=1;e<t.length;e++){const s=t[e].x-t[e-1].x;t[e].slope=s>0?(t[e].z-t[e-1].z)/s*100:0}t.length&&(t[0].slope=t.length>1?t[1].slope:0)}_slopeScale(){const t=this.options.slopeClassSize||2.5,e=this.options.maxClasses||8,s=Math.max(1,Math.floor((this._stats.maxAbsSlope||0)/t)),o=Math.min(s,e-1),i=s>o;let n;if(this.slopeColors&&this.slopeColors.length){const t=r.interpolateRgbBasis(this.slopeColors);n=e=>t(o?e/o:0)}else{const t=r.scaleLinear().domain([0,.16,.42,.68,1]).range(["#2166ac","#27a35a","#ffe000","#f4791f","#d7191c"]).interpolate(r.interpolateRgb).clamp(!0);n=e=>String(t(o?e/o:0))}return{classSize:t,maxIdx:o,capped:i,colorByIndex:n}}_classIndex(t,e){return Math.min(e.maxIdx,Math.floor(Math.abs(t)/e.classSize))}_fmtDuration(t){if(null==t||!isFinite(t))return"";const e=this.options.labels&&this.options.labels.durationUnits||{s:"sec",m:"min",h:"h",d:"j"};if((t=Math.max(0,Math.round(t)))<60)return t+" "+e.s;if(t<3600)return Math.round(t/60)+" "+e.m;if(t<86400){let s=Math.floor(t/3600),o=Math.round(t%3600/60);return 60===o&&(s++,o=0),o?`${s} ${e.h} ${o} ${e.m}`:`${s} ${e.h}`}let s=Math.floor(t/86400),o=Math.round(t%86400/3600);return 24===o&&(s++,o=0),o?`${s} ${e.d} ${o} ${e.h}`:`${s} ${e.d}`}_renderHeader(){const t=this.options,e=this._stats,s=this._feature,o=s.get&&s.get(t.titleProperty)||"Profil",i=t.titleLink&&s.get&&s.get(t.titleLink);m(i)?this._titleEl.innerHTML=`<a href="${d(i)}" target="_blank" rel="noopener">${d(o)}</a>`:this._titleEl.textContent=o,this._titleEl.setAttribute("title",o);const n=[],l=[];if(t.headerItems.forEach(o=>{if("string"==typeof o){if("distance"===o)n.push(`<b>${g(e.distance,t.units)}</b>`),l.push(g(e.distance,t.units));else if("ascent"===o)n.push(`<span class="oep-up">${t.labels.ascent} ${y(e.ascent,t.units)}</span>`),l.push(`${t.labels.ascent} ${y(e.ascent,t.units)}`);else if("descent"===o)n.push(`<span class="oep-down">${t.labels.descent} ${y(e.descent,t.units)}</span>`),l.push(`${t.labels.descent} ${y(e.descent,t.units)}`);else if("min"===o)n.push(y(e.min,t.units)),l.push(y(e.min,t.units));else if("max"===o)n.push(y(e.max,t.units)),l.push(y(e.max,t.units));else if("minmax"===o){const s=`${y(e.min,t.units)}–${y(e.max,t.units)}`;n.push(s),l.push(s)}else if("duration"===o&&null!=e.duration){const s=this._fmtDuration(e.duration);n.push(`<span class="oep-time">${d(t.labels.duration)} ${s}</span>`),l.push(`${t.labels.duration} ${s}`)}}else if(o&&o.property){const t=s.get&&s.get(o.property);if(null==t||""===t)return;const e=o.label?`${o.label} `:"";o.asLink&&m(t)?(n.push(`${e}<a href="${d(t)}" target="_blank" rel="noopener">${d(o.linkText||o.label||t)}</a>`),l.push(`${o.label||""} ${t}`)):(n.push(d(e+t)),l.push(e+t))}}),this._statsEl.innerHTML=n.join(" · "),this._statsEl.setAttribute("title",l.join(" · ")),this._legendEl.innerHTML="",t.slope&&t.slopeLegend){const t=this._slopeScale();for(let e=0;e<=t.maxIdx;e++){const s=t.colorByIndex(e),o=t.capped&&e===t.maxIdx?`≥ ${e*t.classSize} %`:`${e*t.classSize}–${(e+1)*t.classSize} %`,i=document.createElement("span");i.className="oep-leg-it",i.innerHTML=`<i class="oep-sw" style="background:${s}"></i>${o}`,this._legendEl.appendChild(i)}this._legendEl.style.display=""}else this._legendEl.style.display="none"}_render(){const t=this.options,e=this._stats,s=this._samples;if(!s||!s.length)return;this._applyTheme();const o=this._applyPlacement();this._renderHeader();const i=t.margins,n=i.unit||"px",l=t=>"px"===n?t:t*(parseFloat(getComputedStyle(this.element).fontSize)||16),a=this._availWidth(),h="auto"===t.width||"100%"===t.width||"full"===t.width?a:Math.min("number"==typeof t.width?t.width:parseFloat(t.width)||a,a);this.element.style.width=o?"":`${h}px`;const p=this.element.clientWidth||(o?a:h),c=Math.max(220,p-16),u="number"==typeof t.height?t.height:180,d=l(i.top),m=l(i.right),_=l(i.bottom),f=l(i.left),g=Math.max(10,c-f-m),y=Math.max(10,u-d-_),x=null!=t.xTicks?t.xTicks:Math.max(2,Math.round(g/80)),b=null!=t.yTicks?t.yTicks:Math.max(2,Math.round(y/40));this._body.innerHTML="";const M=r.select(this._body).append("svg").attr("class","oep-svg").attr("width",c).attr("height",u).attr("viewBox",`0 0 ${c} ${u}`),k=M.append("g").attr("transform",`translate(${f},${d})`),v=r.scaleLinear().domain([0,e.distance]).range([0,g]),z=.1*(e.max-e.min)||10,$=r.scaleLinear().domain([e.min-z,e.max+z]).range([y,0]).nice();this._x=v,this._y=$,this._dims={innerW:g,innerH:y},t.grid&&k.append("g").attr("class","oep-grid").call(r.axisLeft($).ticks(b).tickSize(-g).tickFormat(""));const E="imperial"===t.units?1609.344:1e3;const C=r.area().x(t=>v(t.x)).y0(y).y1(t=>$(t.z));if(t.slope){const e=this._slopeScale(),o=[];let i=1;for(;i<s.length;){const t=this._classIndex(s[i].slope,e);let n=i;for(;n+1<s.length&&this._classIndex(s[n+1].slope,e)===t;)n++;k.append("path").datum(s.slice(i-1,n+1)).attr("class","oep-area-slope").attr("fill",e.colorByIndex(t)).attr("d",C),i>1&&o.push(s[i-1]),i=n+1}t.slopeSeparators&&o.forEach(t=>{k.append("line").attr("class","oep-slope-sep").attr("x1",v(t.x)).attr("x2",v(t.x)).attr("y1",$(t.z)).attr("y2",y)})}else k.append("path").datum(s).attr("class","oep-area").attr("d",C);k.append("path").datum(s).attr("class","oep-line").attr("d",r.line().x(t=>v(t.x)).y(t=>$(t.z))),k.append("g").attr("class","oep-axis oep-axis-x").attr("transform",`translate(0,${y})`).call(r.axisBottom(v).ticks(x).tickFormat(t=>(t/E).toFixed(t/E<10?1:0))),k.append("text").attr("class","oep-axis-label").attr("x",g).attr("y",y+_-4).attr("text-anchor","end").text((t=>"imperial"===t?"mi":"km")(t.units)),k.append("g").attr("class","oep-axis oep-axis-y").call(r.axisLeft($).ticks(b).tickFormat(e=>"imperial"===t.units?Math.round(3.28084*e):e)),t.zoom&&!this._cropMode&&[["A",this._zoomA],["B",this._zoomB]].forEach(([t,e])=>{if(null==e)return;const s=v(e);k.append("line").attr("class","oep-ab-line").attr("x1",s).attr("x2",s).attr("y1",0).attr("y2",y),k.append("text").attr("class","oep-ab-label").attr("x",s).attr("y",-6).attr("text-anchor","middle").text(t)});const w=k.append("g").attr("class","oep-focus").style("display","none");w.append("line").attr("class","oep-focus-line").attr("y1",0).attr("y2",y),w.append("circle").attr("class","oep-focus-dot").attr("r",4);const S=w.append("g").attr("class","oep-focus-label");S.append("rect").attr("class","oep-focus-bg"),S.append("text").attr("class","oep-focus-txt"),this._focus=w;const T=r.bisector(t=>t.x).left,B=t=>{const s=r.pointer(t,k.node())[0];return Math.max(0,Math.min(e.distance,v.invert(s)))},L=M.append("rect").attr("class","oep-overlay").attr("x",f).attr("y",d).attr("width",g).attr("height",y);L.on("mousemove",t=>{const e=B(t),o=T(s,e,1),i=s[o-1],n=s[o]||i,l=e-i.x>n.x-e?n:i;this._setFocus(l),this._marker&&this._marker.setPosition(l.coord)}).on("mouseout",()=>this._clearFocus()),L.on("click",t=>{if(!this._armed||this._cropMode)return;const e=B(t);"A"===this._armed?this._zoomA=e:this._zoomB=e,this._armed=null,this._updateZoomButtons(),null!=this._zoomA&&null!=this._zoomB?this._applyZoom():this._render()}),this._armed&&L.style("cursor","col-resize"),this._adjustAttribution()}_arm(t){this._armed=this._armed===t?null:t,this._updateZoomButtons(),this._focus&&this._render()}_updateZoomButtons(){const t=!!this.options.zoom&&!!this._feature;this._toolbar.style.display=t?"":"none";const e=this._cropMode;this._btnA.style.display=t&&!e?"":"none",this._btnB.style.display=t&&!e?"":"none",this._btnAll.style.display=t&&e?"":"none",this._btnA.classList.toggle("armed","A"===this._armed),this._btnB.classList.toggle("armed","B"===this._armed)}_applyZoom(){const t=Math.min(this._zoomA,this._zoomB),e=Math.max(this._zoomA,this._zoomB),s=this._fullSamples.filter(s=>s.x>=t&&s.x<=e);if(s.length<2)return;const o=s[0].x,i=null!=s[0].t?s[0].t:0,l=s.map(t=>({x:t.x-o,z:t.z,coord:t.coord,slope:t.slope,t:null!=t.t?t.t-i:null})),a=s.map(t=>t.coord);this._samples=l,this._stats=this._statsOf(l,!1),this._cropMode=!0,this._updateZoomButtons(),this._render();const r=this.getMap();if(r){const t=n.boundingExtent(a),e=r.getView();try{this._fitRes=e.getResolutionForExtent(t,r.getSize())}catch(t){this._fitRes=null}e.fit(t,{padding:[40,40,40,40],duration:400})}}_exitZoom(){this._cropMode=!1,this._zoomA=null,this._zoomB=null,this._armed=null,this._fitRes=null,this._samples=this._fullSamples,this._stats=this._fullStats,this._updateZoomButtons(),this._collapsed||this._render();const t=this.getMap();t&&this._feature&&t.getView().fit(this._feature.getGeometry().getExtent(),{padding:[40,40,40,40],duration:400})}_tooltipText(t){const e=this.options,s=[];return e.tooltipItems.forEach(o=>{var i;"distance"===o?s.push(g(t.x,e.units)):"elevation"===o?s.push(y(t.z,e.units)):"slope"===o?s.push(`${(i=t.slope||0)>=0?"+":""}${i.toFixed(1)} %`):"time"===o&&null!=t.t&&s.push(this._fmtDuration(t.t))}),s.join(" · ")}_setFocus(t){if(!this._focus)return;const e=this._x,s=this._y;this._focus.style("display",null),this._focus.select(".oep-focus-line").attr("x1",e(t.x)).attr("x2",e(t.x)),this._focus.select(".oep-focus-dot").attr("cx",e(t.x)).attr("cy",s(t.z));let o=s(t.z)-16;o<12&&(o=s(t.z)+22);const i=this._focus.select(".oep-focus-txt").attr("x",e(t.x)).attr("y",o).text(this._tooltipText(t)),n=i.node().getBBox();this._focus.select(".oep-focus-bg").attr("x",n.x-4).attr("y",n.y-2).attr("width",n.width+8).attr("height",n.height+4),e(t.x)+n.width/2>this._dims.innerW?i.attr("text-anchor","end"):e(t.x)-n.width/2<0?i.attr("text-anchor","start"):i.attr("text-anchor","middle")}_focusByCoord(t){const e=this._samples;if(!e)return;let s=null,o=1/0;for(const i of e){const e=i.coord[0]-t[0],n=i.coord[1]-t[1],l=e*e+n*n;l<o&&(o=l,s=i)}s&&(this._setFocus(s),this._marker&&this._marker.setPosition(s.coord))}_clearFocus(){this._focus&&this._focus.style("display","none"),this._marker&&this._marker.setPosition(void 0)}_clear(){this._body.innerHTML="",this._legendEl.innerHTML="",this._legendEl.style.display="none",this._titleEl.textContent=this.options.labels.empty,this._titleEl.removeAttribute("title"),this._statsEl.innerHTML="",this._statsEl.removeAttribute("title"),this._cropMode=!1,this._updateZoomButtons(),this._clearFocus(),this.element.style.display="none",this._adjustAttribution()}}return M.addTheme=(t,e)=>{h[t]=e},M.THEMES=h,M.POSITIONS=p,M.version="0.6.0",M});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ol-elevation-profile",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Synchronized elevation profile control for OpenLayers, rendered with d3. Reads elevation directly from 3D GPX/GeoJSON geometries.",
5
5
  "type": "module",
6
6
  "main": "dist/ol-elevation-profile.js",
@@ -108,3 +108,5 @@
108
108
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35); pointer-events: none;
109
109
  }
110
110
  .ol-elevation-profile.oep-floating { transform: none; }
111
+
112
+ .oep-time{opacity:.85}
@@ -53,7 +53,6 @@ import * as d3 from 'd3';
53
53
  slopeSeparators: true, // ligne verticale à chaque changement de classe
54
54
  slopeLegend: true,
55
55
  maxClasses: 8, // nombre maximal de classes de pente (couleurs + légende)
56
- maxLegendClasses: 8,
57
56
  xTicks: null,
58
57
  yTicks: null,
59
58
  show: 'click',
@@ -65,6 +64,8 @@ import * as d3 from 'd3';
65
64
  responsive: true, // adapte la largeur/placement, mobile inclus
66
65
  mobileBreakpoint: 640, // <= largeur écran -> mode mobile (100% largeur, top/bottom)
67
66
  zoom: false, // boutons début/fin pour recadrer carte + profil sur A..B
67
+ ignoreStops: true, // durée = temps en mouvement (ignore les arrêts)
68
+ stopSpeed: 0.5, // seuil d'arrêt en m/s (~1,8 km/h)
68
69
  tooltipItems: ['distance', 'elevation'],
69
70
  headerItems: ['distance', 'ascent', 'descent', 'minmax'],
70
71
  titleProperty: 'name',
@@ -72,6 +73,8 @@ import * as d3 from 'd3';
72
73
  labels: {
73
74
  distance: 'Distance', elevation: 'Altitude', slope: 'Pente',
74
75
  ascent: 'D+', descent: 'D-', empty: 'Cliquez un tracé',
76
+ time: 'Temps', duration: 'Durée',
77
+ durationUnits: { s: 'sec', m: 'min', h: 'h', d: 'j' },
75
78
  zoomStart: 'Définir le début (A)', zoomEnd: 'Définir la fin (B)', zoomAll: 'Tout voir'
76
79
  }
77
80
  };
@@ -89,6 +92,30 @@ import * as d3 from 'd3';
89
92
  const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
90
93
  const isUrl = (v) => typeof v === 'string' && /^(https?:)?\/\/|^mailto:/i.test(v);
91
94
 
95
+ // Extrait un tableau plat de timestamps (ms epoch) aligné sur l'ordre plat des coordonnées,
96
+ // depuis properties.coordTimes (ISO ou nombre), coordinateProperties.times, ou la 4e dimension (M).
97
+ function extractTimes(feature, lines) {
98
+ const props = (feature && feature.getProperties) ? feature.getProperties() : {};
99
+ let raw = props.coordTimes;
100
+ if (raw == null && props.coordinateProperties) raw = props.coordinateProperties.times || props.coordinateProperties.coordTimes;
101
+ let flat = null;
102
+ if (Array.isArray(raw)) flat = Array.isArray(raw[0]) ? raw.reduce((a, b) => a.concat(b), []) : raw.slice();
103
+ if (!flat && lines) { // repli : 4e dimension M (layout XYZM)
104
+ const tmp = []; let any = false;
105
+ for (const seg of lines) for (const c of seg) { const v = c.length > 3 ? c[3] : null; tmp.push(v); if (v != null && isFinite(v)) any = true; }
106
+ flat = any ? tmp : null;
107
+ }
108
+ if (!flat) return null;
109
+ let valid = 0;
110
+ const ms = flat.map((v) => {
111
+ if (v == null || v === '') return null;
112
+ const t = (typeof v === 'number') ? (v > 1e12 ? v : v * 1000) : Date.parse(v); // epoch ms / epoch s / ISO
113
+ if (!isFinite(t)) return null;
114
+ valid++; return t;
115
+ });
116
+ return valid >= 2 ? ms : null;
117
+ }
118
+
92
119
  function colorToCss(c) {
93
120
  if (c == null) return null;
94
121
  if (typeof c === 'string') return c;
@@ -159,8 +186,10 @@ import * as d3 from 'd3';
159
186
  * @property {boolean} [responsive=true] Adapt width/placement; mobile included.
160
187
  * @property {number} [mobileBreakpoint=640] Below this width: mobile mode (100% width, top/bottom only).
161
188
  * @property {boolean} [zoom=false] A/B buttons to crop map + profile to a sub-range.
162
- * @property {Array<'distance'|'elevation'|'slope'>} [tooltipItems=['distance','elevation']] Tooltip content.
163
- * @property {Array<string|{property:string,label?:string,asLink?:boolean,linkText?:string}>} [headerItems] Header content.
189
+ * @property {boolean} [ignoreStops=true] When computing time, ignore stopped segments (moving time).
190
+ * @property {number} [stopSpeed=0.5] Speed threshold (m/s) below which a segment counts as a stop.
191
+ * @property {Array<'distance'|'elevation'|'slope'|'time'>} [tooltipItems=['distance','elevation']] Tooltip content (`'time'` = elapsed time at the cursor, if the track has time data).
192
+ * @property {Array<string|{property:string,label?:string,asLink?:boolean,linkText?:string}>} [headerItems] Header content (string tokens: distance, ascent, descent, min, max, minmax, `'duration'` = total elapsed time).
164
193
  * @property {string} [titleProperty='name'] Feature property used as the title.
165
194
  * @property {?string} [titleLink=null] Feature property holding a URL → clickable title.
166
195
  * @property {number} [maxPoints=2000] Decimation for render/interaction (stats use full data).
@@ -211,6 +240,18 @@ import * as d3 from 'd3';
211
240
  return false;
212
241
  }
213
242
 
243
+ /**
244
+ * Whether a feature carries per-point time data (coordTimes / XYZM).
245
+ * @param {import('ol/Feature').default} feature
246
+ * @returns {boolean}
247
+ */
248
+ static featureHasTime(feature) {
249
+ const g = feature && feature.getGeometry && feature.getGeometry();
250
+ if (!g) return false;
251
+ const lines = g.getType() === 'MultiLineString' ? g.getCoordinates() : [g.getCoordinates()];
252
+ return !!extractTimes(feature, lines);
253
+ }
254
+
214
255
  // ---------- DOM ---------------------------------------------------
215
256
  _buildDom(root) {
216
257
  const o = this.options;
@@ -426,13 +467,28 @@ import * as d3 from 'd3';
426
467
  const lines = geom.getType() === 'MultiLineString' ? geom.getCoordinates() : [geom.getCoordinates()];
427
468
  const dataProj = o.dataProjection || (this.getMap() && this.getMap().getView().getProjection()) || 'EPSG:3857';
428
469
 
470
+ const times = extractTimes(this._feature, lines);
471
+ this._hasTime = !!times;
472
+ const ignoreStops = o.ignoreStops !== false; // défaut : ignore les arrêts
473
+ const stopSpeed = (o.stopSpeed != null ? o.stopSpeed : 0.5); // m/s
429
474
  const pts = [];
430
- let cum = 0, prev = null;
475
+ let cum = 0, prev = null, i = -1, tAcc = 0, prevMs = null;
431
476
  for (const seg of lines) for (const c of seg) {
477
+ i++;
432
478
  const ll = toLonLat(c, dataProj);
433
- if (prev) cum += getDistance(prev, ll);
479
+ let dseg = 0;
480
+ if (prev) { dseg = getDistance(prev, ll); cum += dseg; }
481
+ let t = null;
482
+ if (times && times[i] != null) {
483
+ const ms = times[i];
484
+ if (prevMs != null) {
485
+ const dt = (ms - prevMs) / 1000; // s sur le segment
486
+ if (dt > 0 && (!ignoreStops || (dseg / dt) >= stopSpeed)) tAcc += dt;
487
+ }
488
+ t = tAcc; prevMs = ms;
489
+ }
434
490
  prev = ll;
435
- pts.push({ x: cum, z: (c.length > 2 && isFinite(c[2])) ? c[2] : 0, coord: c });
491
+ pts.push({ x: cum, z: (c.length > 2 && isFinite(c[2])) ? c[2] : 0, coord: c, t });
436
492
  }
437
493
  if (o.smoothing > 0) this._smooth(pts, o.smoothing);
438
494
 
@@ -450,7 +506,9 @@ import * as d3 from 'd3';
450
506
  const s = Math.abs(samples[k].slope || 0); if (s > maxAbs) maxAbs = s;
451
507
  }
452
508
  const distance = samples.length ? samples[samples.length - 1].x - samples[0].x : 0;
453
- return { distance, ascent, descent, min: isFinite(zmin) ? zmin : 0, max: isFinite(zmax) ? zmax : 0, maxAbsSlope: maxAbs, points: samples.length };
509
+ const ta = samples.length ? samples[0].t : null, tb = samples.length ? samples[samples.length - 1].t : null;
510
+ const duration = (ta != null && tb != null) ? (tb - ta) : null;
511
+ return { distance, duration, ascent, descent, min: isFinite(zmin) ? zmin : 0, max: isFinite(zmax) ? zmax : 0, maxAbsSlope: maxAbs, points: samples.length };
454
512
  }
455
513
  _smooth(pts, meters) {
456
514
  if (!(meters > 0) || pts.length < 3) return;
@@ -465,7 +523,7 @@ import * as d3 from 'd3';
465
523
  }
466
524
  }
467
525
  _decimate(pts, maxPoints) {
468
- const copy = (p) => ({ x: p.x, z: p.z, coord: p.coord });
526
+ const copy = (p) => ({ x: p.x, z: p.z, coord: p.coord, t: p.t });
469
527
  if (!maxPoints || pts.length <= maxPoints) return pts.map(copy);
470
528
  const step = pts.length / maxPoints, out = [];
471
529
  for (let i = 0; i < maxPoints; i++) out.push(copy(pts[Math.floor(i * step)]));
@@ -504,6 +562,24 @@ import * as d3 from 'd3';
504
562
  }
505
563
  _classIndex(slope, sc) { return Math.min(sc.maxIdx, Math.floor(Math.abs(slope) / sc.classSize)); }
506
564
 
565
+ // ---------- temps : format adaptatif ------------------------------
566
+ // 7 sec · 26 min · 1 h 48 min · 2 j 3 h (jours + heures normalisés)
567
+ _fmtDuration(sec) {
568
+ if (sec == null || !isFinite(sec)) return '';
569
+ const u = (this.options.labels && this.options.labels.durationUnits) || { s: 'sec', m: 'min', h: 'h', d: 'j' };
570
+ sec = Math.max(0, Math.round(sec));
571
+ if (sec < 60) return sec + ' ' + u.s;
572
+ if (sec < 3600) return Math.round(sec / 60) + ' ' + u.m;
573
+ if (sec < 86400) {
574
+ let h = Math.floor(sec / 3600), m = Math.round((sec % 3600) / 60);
575
+ if (m === 60) { h++; m = 0; }
576
+ return m ? `${h} ${u.h} ${m} ${u.m}` : `${h} ${u.h}`;
577
+ }
578
+ let d = Math.floor(sec / 86400), h = Math.round((sec % 86400) / 3600);
579
+ if (h === 24) { d++; h = 0; }
580
+ return h ? `${d} ${u.d} ${h} ${u.h}` : `${d} ${u.d}`;
581
+ }
582
+
507
583
  // ---------- entête + légende --------------------------------------
508
584
  _renderHeader() {
509
585
  const o = this.options, s = this._stats, f = this._feature;
@@ -522,6 +598,7 @@ import * as d3 from 'd3';
522
598
  else if (it === 'min') { html.push(fmtElevation(s.min, o.units)); text.push(fmtElevation(s.min, o.units)); }
523
599
  else if (it === 'max') { html.push(fmtElevation(s.max, o.units)); text.push(fmtElevation(s.max, o.units)); }
524
600
  else if (it === 'minmax') { const v = `${fmtElevation(s.min, o.units)}–${fmtElevation(s.max, o.units)}`; html.push(v); text.push(v); }
601
+ else if (it === 'duration') { if (s.duration != null) { const v = this._fmtDuration(s.duration); html.push(`<span class="oep-time">${esc(o.labels.duration)} ${v}</span>`); text.push(`${o.labels.duration} ${v}`); } }
525
602
  } else if (it && it.property) {
526
603
  const val = f.get && f.get(it.property); if (val == null || val === '') return;
527
604
  const lbl = it.label ? `${it.label} ` : '';
@@ -662,7 +739,8 @@ import * as d3 from 'd3';
662
739
  const raw = this._fullSamples.filter((p) => p.x >= a && p.x <= b);
663
740
  if (raw.length < 2) return;
664
741
  const off = raw[0].x; // A devient 0
665
- const cropped = raw.map((p) => ({ x: p.x - off, z: p.z, coord: p.coord, slope: p.slope }));
742
+ const tOff = raw[0].t != null ? raw[0].t : 0;
743
+ const cropped = raw.map((p) => ({ x: p.x - off, z: p.z, coord: p.coord, slope: p.slope, t: (p.t != null ? p.t - tOff : null) }));
666
744
  const coords = raw.map((p) => p.coord);
667
745
  this._samples = cropped; this._stats = this._statsOf(cropped, false); this._cropMode = true;
668
746
  this._updateZoomButtons(); this._render();
@@ -689,6 +767,7 @@ import * as d3 from 'd3';
689
767
  if (it === 'distance') parts.push(fmtDistance(d.x, o.units));
690
768
  else if (it === 'elevation') parts.push(fmtElevation(d.z, o.units));
691
769
  else if (it === 'slope') parts.push(fmtSlope(d.slope || 0));
770
+ else if (it === 'time') { if (d.t != null) parts.push(this._fmtDuration(d.t)); }
692
771
  });
693
772
  return parts.join(' · ');
694
773
  }
@@ -733,6 +812,6 @@ import * as d3 from 'd3';
733
812
  ElevationProfile.addTheme = (name, colors) => { THEMES[name] = colors; };
734
813
  ElevationProfile.THEMES = THEMES;
735
814
  ElevationProfile.POSITIONS = POSITIONS;
736
- ElevationProfile.version = '0.5.0';
815
+ ElevationProfile.version = '0.6.0';
737
816
 
738
817
  export default ElevationProfile;