map11 = {
const container = htl.html`<div style="height: 600px; position: relative; width: 900px">
<button id="playButton" style="position: absolute; bottom: 10px; left: 10px; z-index: 1000;">Play</button>
<button id="restartButton" style="position: absolute; bottom: 10px; left: 80px; z-index: 1000;">Restart</button>
<select id="birdSelect" style="position: absolute; top: 100px; left: 10px; z-index: 1000; width: 200px;">
<option value="" selected disabled>Select bird ID</option>
</select>
<div id="colorLegend" style="position: absolute; top: 150px; left: 10px; z-index: 1000;">
</div>
</div>`;
const map = L.map(container).setView([40, -190], 2);
setTimeout(() => map.invalidateSize(true), 0);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors"
}).addTo(map);
let intervalId;
let isPlaying = false;
let markersLayer = L.layerGroup().addTo(map);
let markers = [];
let index = 0;
const birdSelect = container.querySelector('#birdSelect');
const uniqueBirdIds = Array.from(new Set(adjustedData.map(d => d.animal_id.trim())));
uniqueBirdIds.forEach(id => {
const option = document.createElement('option');
option.value = id;
option.textContent = id;
birdSelect.appendChild(option);
});
const animateTrajectories = (filteredData, map, colorScale) => {
markersLayer.clearLayers(); // Clear previous markers and polylines
markers = []; // Reset markers array
index = 0; // Reset index
const groupedData = d3.group(filteredData, d => d.animal_id);
groupedData.forEach((values, key) => {
// Sort values by timestamp
values.sort((a, b) => a.timestamp - b.timestamp);
let latlngs = [];
values.forEach((d, i) => {
markers.push({
latlng: [d.latitude, d.longitude],
color: colorScale(d.timestamp),
popup: `Animal ID: ${d.animal_id}<br>Timestamp: ${new Date(d.timestamp).toISOString().substring(0, 10)}<br>Latitude: ${d.latitude}<br>Longitude: ${d.longitude}`,
latlngs
});
});
});
const birdIcon = L.icon({
iconUrl: northernFul.src, // 设置鸟的图标路径
iconSize: [100, 100], // 设置图标的尺寸
iconAnchor: [25, 25], // 设置图标的锚点
popupAnchor: [0, -15]
});
const step = () => {
if (index < markers.length) {
const markerData = markers[index];
markerData.latlngs.push(markerData.latlng);
// 移除之前的 bird marker
markersLayer.eachLayer(layer => {
if (layer instanceof L.Marker && layer.options.icon === birdIcon) {
markersLayer.removeLayer(layer);
}
});
const marker = L.circleMarker(markerData.latlng, {
radius: 3,
color: markerData.color,
fillOpacity: 0.5
}).addTo(markersLayer).bindPopup(markerData.popup);
if (markerData.latlngs.length > 1) {
const segment = [markerData.latlngs[markerData.latlngs.length - 2], markerData.latlng];
L.polyline(segment, { color: markerData.color }).addTo(markersLayer);
}
// 创建新的图标
const birdMarker = L.marker(markerData.latlng, {
icon: birdIcon // 使用自定义图标
}).addTo(markersLayer).bindPopup(markerData.popup);
index++;
} else {
clearInterval(intervalId);
alert("The trajectory has finished playing.");
}
};
intervalId = setInterval(step, 500); // Adjust the time interval as needed
// Add legend
const colorLegend = container.querySelector('#colorLegend');
colorLegend.innerHTML = '';
const colorScaleTicks = colorScale.ticks(10); // 设置图例的标记
colorScaleTicks.forEach((tick) => {
// 创建包含颜色方块和标签的父容器
const legendItem = document.createElement('div');
legendItem.style.display = 'flex'; // 将子元素排列在一行
legendItem.style.alignItems = 'center'; // 垂直居中对齐
// 创建颜色方块
const colorDiv = document.createElement('div');
const color = colorScale(tick);
colorDiv.style.backgroundColor = color;
colorDiv.style.width = '20px';
colorDiv.style.height = '20px';
legendItem.appendChild(colorDiv);
// 创建标签
const labelDiv = document.createElement('div');
labelDiv.textContent = new Date(tick).toISOString().substring(0, 10);
labelDiv.style.marginLeft = '5px'; // 添加一些间距
legendItem.appendChild(labelDiv);
// 将父容器添加到颜色图例和标签的父容器中
colorLegend.appendChild(legendItem);
});
};
// Event listener for play/pause button
const playButton = container.querySelector('#playButton');
playButton.addEventListener('click', () => {
if (isPlaying) {
clearInterval(intervalId);
playButton.textContent = 'Play';
} else {
if (index === 0) {
const selectedBirdId = birdSelect.value;
const filteredData = adjustedData.filter(d => d.animal_id === selectedBirdId);
if (filteredData.length > 0) {
const timeExtent = d3.extent(filteredData, d => d.timestamp);
const colorScale = d3.scaleSequential(d3.interpolateViridis).domain(timeExtent);
animateTrajectories(filteredData, map, colorScale);
// Highlight data points in chart7
const circles = d3.selectAll("circle");
circles.filter(d => d.animal_id === selectedBirdId)
.attr("fill-opacity", 1);
circles.filter(d => d.animal_id !== selectedBirdId)
.attr("fill-opacity", 0.05);
} else {
alert('No data found for the specified bird ID.');
return;
}
} else {
intervalId = setInterval(() => {
if (index < markers.length) {
const markerData = markers[index];
markerData.latlngs.push(markerData.latlng);
const marker = L.circleMarker(markerData.latlng, {
radius: 3,
color: markerData.color,
fillOpacity: 0.8
}).addTo(markersLayer).bindPopup(markerData.popup);
if (markerData.latlngs.length > 1) {
const segment = [markerData.latlngs[markerData.latlngs.length - 2], markerData.latlng];
L.polyline(segment, { color: markerData.color }).addTo(markersLayer);
}
index++;
} else {
clearInterval(intervalId);
alert("The trajectory has finished playing.");
}
}, 500); // Adjust the time interval as needed
}
playButton.textContent = 'Pause';
}
isPlaying = !isPlaying;
});
// Event listener for restart button
const restartButton = container.querySelector('#restartButton');
restartButton.addEventListener('click', () => {
clearInterval(intervalId);
playButton.textContent = 'Play';
markersLayer.clearLayers(); // Clear all markers and polylines
index = 0;
isPlaying = false;
// Reset data points opacity in chart7
const circles = d3.selectAll("circle");
circles.attr("fill-opacity", 1);
});
return container;
}