-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathanimateUI.js
More file actions
240 lines (209 loc) · 8.6 KB
/
animateUI.js
File metadata and controls
240 lines (209 loc) · 8.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/****************************************************************************
* animateUI.js
* March 2023
*****************************************************************************/
/* global L */
const mapboxAccessToken = 'pk.eyJ1Ijoiamdyb3Nza3JldXoiLCJhIjoiY2tseWIxNTRoMHFvODJxbHlyanRobzBmZiJ9.xz2KrKBy5MRCf9XLOOPdzA';
const warningDisplay = document.getElementById('warning-display');
const warningText = document.getElementById('warning-text');
const playButton = document.getElementById('play-button');
const durationInput = document.getElementById('duration-input');
const followCheckbox = document.getElementById('follow-checkbox');
const hideMapControlsCheckbox = document.getElementById('hide-map-controls-checkbox');
const hideMapScaleCheckbox = document.getElementById('hide-map-scale-checkbox');
const colorInput = document.getElementById('color-input');
const weightInput = document.getElementById('weight-input');
const opacityInput = document.getElementById('opacity-input');
const loopCheckbox = document.getElementById('loop-checkbox');
const pointLimitInput = document.getElementById('point-limit-input');
const datetimeSpan = document.getElementById('datetime-span');
// Get colors of AIMS color scheme from CSS variables
const lineColour = window.getComputedStyle(warningDisplay).getPropertyValue('--aims-purple');
const circleColour = window.getComputedStyle(warningDisplay).getPropertyValue('--aims-pink');
// Initialize overlay layer for the track as polyline
const lineLayer = L.layerGroup();
// Map variables
var mapLayers;
var map;
// Animation variables
var polylineArray = null;
var polyline = null;
var animationInterval = null;
var animationStartTime = null;
var dateArray = null;
/**
* Request tile layer to display on a Leaflet map
* @param {string} id Leaflet title layer ID
* @returns Tile layer object
*/
function getTileLayer(id) {
// Retrieve specific base map layer from mapbox API
return L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
id: id,
accessToken: mapboxAccessToken
});
}
/**
* Get all tile layers a user could choose to display on a map
* @returns A JSON object of tile layers
*/
function getTileLayers() {
return {
Streets: getTileLayer('mapbox/streets-v11'),
Outdoors: getTileLayer('mapbox/outdoors-v11'),
Light: getTileLayer('mapbox/light-v10'),
Dark: getTileLayer('mapbox/dark-v10'),
Satellite: getTileLayer('mapbox/satellite-v9'),
'Satellite & Streets': getTileLayer('mapbox/satellite-streets-v11')
};
}
// Prepare page by first loading the map
mapLayers = getTileLayers();
// Create map with some overlay layers and one of the base layers.
map = L.map('display-map', {
layers: [mapLayers.Dark, lineLayer]
});
var overlayMaps = {
Track: lineLayer
};
// Add control to select base map layer and to disable overlay layers
var layerControl = L.control.layers(mapLayers, overlayMaps).addTo(map);
// Add map scale
var scaleControl = L.control.scale({ position: 'bottomleft' }).addTo(map);
// Get zoom cotrol handle
var zoomControl = map.zoomControl;
// Set default line color
colorInput.value = lineColour;
// Load track from local storage if it exists
if (localStorage.getItem('pointList') !== null) {
// Add track as polyline to map
polylineArray = JSON.parse(localStorage.getItem('pointList'));
polyline = new L.Polyline(polylineArray, {
color: lineColour,
weight: 3,
opacity: 1.0,
smoothFactor: 1,
interactive: false
});
lineLayer.addLayer(polyline);
// Fit map to track and center it
map.fitBounds(polyline.getBounds().pad(0.1), { maxZoom: 16 });
// Enable inputs and buttons
durationInput.disabled = false;
playButton.disabled = false;
followCheckbox.disabled = false;
colorInput.disabled = false;
weightInput.disabled = false;
opacityInput.disabled = false;
loopCheckbox.disabled = false;
pointLimitInput.disabled = false;
hideMapControlsCheckbox.disabled = false;
hideMapScaleCheckbox.disabled = false;
} else {
// If no plausible point exist, show whole world
map.setView([0, 0], 1);
// Display warning message
warningText.innerHTML = 'No track to display. Go to the <a class="text-link" href="/view">download page</a> and select a track.';
warningDisplay.style.display = '';
}
// Load datetimes from local storage if they exist
if (localStorage.getItem('dateList') !== null) {
dateArray = JSON.parse(localStorage.getItem('dateList'));
const timestamp = dateArray[dateArray.length - 1];
const currentDatetime = new Date(timestamp * 1000);
datetimeSpan.innerText = (currentDatetime === null) ? '-' : currentDatetime.toUTCString().replace('GMT', 'UTC');
} else {
datetimeSpan.innerText = '-';
dateArray = null;
}
// Add event listener to play button
playButton.addEventListener('click', function () {
// If no track is loaded, do nothing
if (polyline !== null) {
if (animationInterval !== null) {
clearInterval(animationInterval);
}
animationStartTime = Date.now();
// Set duration of animation in milliseconds
const duration = parseInt(durationInput.value) * 1000;
// Initialize animation
const polylineLength = polylineArray.length;
let currentVertex = 0;
let currentIteration = 0;
// Asynchronously animate polyline
animationInterval = setInterval(function () {
const elapsedTime = Date.now() - animationStartTime;
const fraction = elapsedTime / duration - currentIteration;
const vertexIndex = Math.min(Math.floor(fraction * polylineLength), polylineLength - 1);
if (vertexIndex !== currentVertex) {
const pointLimit = parseInt(pointLimitInput.value);
if (isNaN(pointLimit) || pointLimit < 1) {
// No point limit, show all points
polyline.setLatLngs(polylineArray.slice(0, vertexIndex + 1));
} else {
// Limit number of points to show
polyline.setLatLngs(polylineArray.slice(Math.max(0, vertexIndex - pointLimit), vertexIndex + 1));
}
currentVertex = vertexIndex;
if (followCheckbox.checked) {
// Center map on current point
map.panTo(polylineArray[vertexIndex]);
}
if (dateArray !== null) {
const timestamp = dateArray[currentVertex];
const currentDatetime = new Date(timestamp * 1000);
datetimeSpan.innerText = (currentDatetime === null) ? '-' : currentDatetime.toUTCString().replace('GMT', 'UTC');
}
}
// Check if animation is finished
if (fraction >= 1) {
if (loopCheckbox.checked) {
// Keep looping
currentIteration++;
} else {
clearInterval(animationInterval);
animationInterval = null;
animationStartTime = null;
}
}
}, 40); // 40 ms = 25 fps
}
});
// Add an event listener to the checkbox to toggle the layer and zoom controls
hideMapControlsCheckbox.addEventListener('change', function () {
if (this.checked) {
map.removeControl(layerControl);
map.removeControl(zoomControl);
} else {
layerControl.addTo(map);
zoomControl.addTo(map);
}
});
// Add an event listener to the checkbox to toggle the map scale
hideMapScaleCheckbox.addEventListener('change', function () {
if (this.checked) {
map.removeControl(scaleControl);
} else {
scaleControl.addTo(map);
}
}
);
// Add an event listener to the color input to change the color of the track
colorInput.addEventListener('change', function () {
if (polyline !== null) {
polyline.setStyle({ color: this.value });
}
});
// Add an event listener to the weight input to change the weight of the track
weightInput.addEventListener('change', function () {
if (polyline !== null) {
polyline.setStyle({ weight: this.value });
}
});
// Add an event listener to the opacity input to change the opacity of the track
opacityInput.addEventListener('change', function () {
if (polyline !== null) {
polyline.setStyle({ opacity: this.value });
}
});