ol-elevation-profile 0.6.0 → 1.1.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 +27 -1
- package/dist/ol-elevation-profile.css +22 -6
- package/dist/ol-elevation-profile.esm.js +960 -63
- package/dist/ol-elevation-profile.esm.min.js +7 -5
- package/dist/ol-elevation-profile.js +962 -66
- package/dist/ol-elevation-profile.min.js +7 -5
- package/package.json +4 -3
- package/src/ol-elevation-profile.css +22 -6
- package/src/ol-elevation-profile.js +960 -63
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
A synchronized, themeable **elevation profile control for [OpenLayers](https://openlayers.org/)**, rendered with [d3](https://d3js.org/).
|
|
11
11
|
|
|
12
|
-
It reads elevation (**Z**) directly from a track's geometry (`[lon, lat, z]` GPX/GeoJSON)
|
|
12
|
+
It reads elevation (**Z**) directly from a track's geometry (`[lon, lat, z]` GPX/GeoJSON) — distance, ascent/descent and min/max are computed from the track itself, with no service involved. A track that carries **no** Z is filled from keyless [terrain tiles](#terrain-model), which is on by default; `dem: null` turns it off. Clicking (or hovering) a track shows its profile; a marker stays synchronized on both the map and the chart, and a click on the empty map hides it. The control is fully responsive (full-width docked bar on phones), supports slope-class colouring, metric smoothing, an A↔B crop, six themes, transparency, and a track-colour mode.
|
|
13
13
|
|
|
14
14
|
## Screenshots
|
|
15
15
|
|
|
@@ -82,6 +82,32 @@ With `slope: true`, the profile is split into contiguous portions of the same sl
|
|
|
82
82
|
|
|
83
83
|
`smoothing` is a sliding-window average over **metres** of track (`0` = none, the default). Because the unit is metric, the result is independent of GPS point density; it softens both the profile and the slope.
|
|
84
84
|
|
|
85
|
+
### Terrain model
|
|
86
|
+
|
|
87
|
+
A track with **no Z** — drawn by hand, traced over a basemap, exported by a tool that drops the third dimension — would get no profile. The missing elevations are read from [AWS Terrain Tiles](https://registry.opendata.aws/terrain-tiles/) instead, **by default**: PNG tiles carrying elevation in their R/G/B channels, **no API key, no quota, no rate limit**. A 10 000-point track costs a handful of tiles where a free elevation API would cost 100 requests. Pass `dem: null` to disable it and keep the control off the network.
|
|
88
|
+
|
|
89
|
+
Elevation is interpolated bilinearly between the four surrounding pixels — reading the containing pixel would make the profile advance in stairs, and every stair counts as a climb then a descent in the D+. A track is filled **entirely or not at all**: a profile missing a few points dives to sea level and its D+ becomes absurd. Tracks that already carry their own Z are untouched.
|
|
90
|
+
|
|
91
|
+
Accuracy is roughly 30–90 m depending on the region (mean 16 m from IGN's 1 m reference on steep alpine terrain). Any of these can be used instead:
|
|
92
|
+
|
|
93
|
+
| `dem` | Source |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `'terrarium'` (default) | AWS Terrain Tiles, keyless, worldwide |
|
|
96
|
+
| `'ign'` | IGN Géoplateforme RGE ALTI, France, metre-accurate, keyless |
|
|
97
|
+
| `{ url: '.../{z}/{x}/{y}.png' }` | any XYZ tile set, `terrarium` or `mapbox` encoding, or your own decoder |
|
|
98
|
+
| `{ wms: { url, layers } }` | WMS tiles, one `GetMap` per tile |
|
|
99
|
+
| `{ olSource }` | any `ol/source/TileImage`, or **`ol/source/GeoTIFF`** for a COG or a WCS `GetCoverage` |
|
|
100
|
+
| `{ featureInfo: { url, layers } }` | GeoServer greyscale coverage, through WMS `GetFeatureInfo` |
|
|
101
|
+
| a function | you fetch the elevations yourself, from any API |
|
|
102
|
+
|
|
103
|
+
Every source but `featureInfo` is sampled with **bilinear interpolation** between the four surrounding pixels. See the [guide](https://lc-4918.github.io/ol-elevation-profile/guide/features#terrain-model).
|
|
104
|
+
|
|
105
|
+
### PNG export
|
|
106
|
+
|
|
107
|
+
`exportPng: true` adds a button to the toolbar, to the right of the zoom buttons, saving the **whole panel** as an image: title, stats line, slope legend and the complete chart with both axes. `profile.exportPNG()` does the same from code and resolves with the `Blob`; `{ download: false }` returns it without saving, `scale` defaults to the device pixel ratio.
|
|
108
|
+
|
|
109
|
+
The position indicator is left out: it marks where the pointer happens to be, which means nothing once the image is saved. The panel is rebuilt as an SVG rather than screenshotted, with every painting property frozen inline, since a serialized SVG carries no stylesheet.
|
|
110
|
+
|
|
85
111
|
### Attributions
|
|
86
112
|
|
|
87
113
|
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.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}
|
|
22
22
|
.ol-elevation-profile.oep-transparent { backdrop-filter: blur(2px); }
|
|
23
23
|
|
|
24
|
-
/* ---- 8
|
|
24
|
+
/* ---- 8 anchors ---- */
|
|
25
25
|
.ol-elevation-profile.oep-pos-bottom { left: 50%; bottom: .6em; transform: translateX(-50%); }
|
|
26
26
|
.ol-elevation-profile.oep-pos-top { left: 50%; top: .6em; transform: translateX(-50%); }
|
|
27
27
|
.ol-elevation-profile.oep-pos-left { left: .6em; top: 50%; transform: translateY(-50%); }
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
.ol-elevation-profile.oep-pos-bottom-left { left: .6em; bottom: .6em; }
|
|
32
32
|
.ol-elevation-profile.oep-pos-bottom-right { right: .6em; bottom: .6em; }
|
|
33
33
|
|
|
34
|
-
/* ---- mobile
|
|
34
|
+
/* ---- mobile: full width, pinned to the top or the bottom ---- */
|
|
35
35
|
.ol-elevation-profile.oep-mobile { left: 0 !important; right: 0 !important; width: auto !important; transform: none !important; }
|
|
36
36
|
.ol-elevation-profile.oep-mobile.oep-pos-bottom { bottom: 0; border-radius: 10px 10px 0 0; }
|
|
37
37
|
.ol-elevation-profile.oep-mobile.oep-pos-top { top: 0; border-radius: 0 0 10px 10px; }
|
|
38
38
|
|
|
39
|
-
/* ----
|
|
39
|
+
/* ---- header ---- */
|
|
40
40
|
.oep-header { display: flex; align-items: center; gap: 8px; width: 100%; padding: 0 2px 2px; }
|
|
41
41
|
.oep-title { flex: 0 1 auto; min-width: 0; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
42
42
|
.oep-title a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
.oep-stats .oep-up { color: #c0392b; }
|
|
46
46
|
.oep-stats .oep-down { color: #2471a3; }
|
|
47
47
|
|
|
48
|
-
/* ----
|
|
48
|
+
/* ---- zoom toolbar ---- */
|
|
49
49
|
.oep-toolbar { flex: 0 0 auto; display: inline-flex; gap: 2px; margin-left: auto; }
|
|
50
50
|
.oep-tbtn {
|
|
51
51
|
display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 22px;
|
|
@@ -65,14 +65,14 @@
|
|
|
65
65
|
.oep-toggle:hover { opacity: 1; background: rgba(0, 0, 0, .07); }
|
|
66
66
|
.oep-toggle svg { width: 15px; height: 15px; display: block; }
|
|
67
67
|
|
|
68
|
-
/* ----
|
|
68
|
+
/* ---- slope legend ---- */
|
|
69
69
|
.oep-legend { display: flex; flex-wrap: wrap; gap: 4px 10px; padding: 0 2px 4px; font-size: 10px; opacity: .9; }
|
|
70
70
|
.oep-leg-it { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
|
|
71
71
|
.oep-sw { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
|
|
72
72
|
|
|
73
73
|
.oep-body { overflow: hidden; }
|
|
74
74
|
|
|
75
|
-
/* ---- mode
|
|
75
|
+
/* ---- collapsed mode: the control shrinks to title + button width ---- */
|
|
76
76
|
.ol-elevation-profile.oep-collapsed { width: auto !important; }
|
|
77
77
|
.ol-elevation-profile.oep-collapsed .oep-body,
|
|
78
78
|
.ol-elevation-profile.oep-collapsed .oep-legend { display: none !important; }
|
|
@@ -80,6 +80,22 @@
|
|
|
80
80
|
.ol-elevation-profile.oep-collapsed .oep-toolbar { display: none !important; }
|
|
81
81
|
.ol-elevation-profile.oep-collapsed .oep-header { width: auto; }
|
|
82
82
|
|
|
83
|
+
/* ---- terrain-model loading spinner ---- */
|
|
84
|
+
/* Takes --oep-area, so it wears the theme colour - and the track colour under color:'auto'. */
|
|
85
|
+
.oep-loading { display: flex; align-items: center; justify-content: center; }
|
|
86
|
+
.oep-spinner {
|
|
87
|
+
width: 26px; height: 26px; box-sizing: border-box;
|
|
88
|
+
border: 3px solid var(--oep-area);
|
|
89
|
+
border-top-color: transparent;
|
|
90
|
+
border-radius: 50%;
|
|
91
|
+
opacity: .9;
|
|
92
|
+
animation: oep-spin .8s linear infinite;
|
|
93
|
+
}
|
|
94
|
+
@keyframes oep-spin { to { transform: rotate(360deg); } }
|
|
95
|
+
/* A still ring reads as a broken control rather than a wait, so the motion stays -
|
|
96
|
+
slowed to a crawl, which is what the reduced-motion request is really about. */
|
|
97
|
+
@media (prefers-reduced-motion: reduce) { .oep-spinner { animation-duration: 4s; } }
|
|
98
|
+
|
|
83
99
|
/* ---- chart ---- */
|
|
84
100
|
.oep-svg { display: block; }
|
|
85
101
|
.oep-area { fill: var(--oep-area); opacity: .6; }
|