Pre timeControl

This commit is contained in:
Vasilisk9812 2025-07-02 20:44:12 +09:00
parent aa0ff91a7d
commit e428f55580
4 changed files with 85 additions and 222 deletions

15
package-lock.json generated
View file

@ -13,6 +13,7 @@
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet-heatmap": "^1.0.0", "leaflet-heatmap": "^1.0.0",
"leaflet-timedimension": "^1.1.1",
"leaflet-velocity": "^2.1.4", "leaflet-velocity": "^2.1.4",
"leaflet.heat": "^0.2.0" "leaflet.heat": "^0.2.0"
}, },
@ -1078,6 +1079,11 @@
"@types/estree": "^1.0.6" "@types/estree": "^1.0.6"
} }
}, },
"node_modules/iso8601-js-period": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/iso8601-js-period/-/iso8601-js-period-0.2.1.tgz",
"integrity": "sha512-iDyz2TQFBd5WhCZjruOwHj01JkQGu7YbVLCVdpA7lCGEcBzE3ffCPAhLh/M8TAp//kCixPpYN4XU54WHCxvD2Q=="
},
"node_modules/js-cookie": { "node_modules/js-cookie": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@ -1109,6 +1115,15 @@
"leaflet": "*" "leaflet": "*"
} }
}, },
"node_modules/leaflet-timedimension": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/leaflet-timedimension/-/leaflet-timedimension-1.1.1.tgz",
"integrity": "sha512-ejXldN94veRsWka1vpC+4rbH+2+3d3ztn2xYx4jcXtjYDrKC/sNnoqCmyH2UEYIy51PI2851aI2k8uGdOEbhlw==",
"dependencies": {
"iso8601-js-period": "^0.2.1",
"leaflet": "~0.7.4 || ~1"
}
},
"node_modules/leaflet-velocity": { "node_modules/leaflet-velocity": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/leaflet-velocity/-/leaflet-velocity-2.1.4.tgz", "resolved": "https://registry.npmjs.org/leaflet-velocity/-/leaflet-velocity-2.1.4.tgz",

View file

@ -26,6 +26,7 @@
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet-heatmap": "^1.0.0", "leaflet-heatmap": "^1.0.0",
"leaflet-timedimension": "^1.1.1",
"leaflet-velocity": "^2.1.4", "leaflet-velocity": "^2.1.4",
"leaflet.heat": "^0.2.0" "leaflet.heat": "^0.2.0"
} }

View file

@ -1,215 +0,0 @@
<!-- Timeline.svelte -->
<script>
import { onMount, onDestroy } from 'svelte';
export let initialHour = 0;
export let totalHours = 72; // 3 суток
export let updateInterval = 3; // шаг в 3 часа
export let playSpeed = 2000; // скорость анимации (мс)
let currentHour = initialHour;
let isPlaying = false;
let timer;
// События для коммуникации с родительским компонентом
const dispatch = createEventDispatcher();
// Автоматическое проигрывание
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
timer = setInterval(() => {
currentHour = (currentHour + updateInterval) % totalHours;
dispatchTimeUpdate();
}, playSpeed);
} else {
clearInterval(timer);
}
}
function dispatchTimeUpdate() {
dispatch('timeupdate', {
hour: currentHour,
timestamp: calculateTimestamp(currentHour)
});
}
function calculateTimestamp(hour) {
const now = new Date();
now.setHours(now.getHours() + hour);
return now;
}
function formatTime(hours) {
const days = Math.floor(hours / 24);
const remainingHours = hours % 24;
const time = calculateTimestamp(hours);
return `+${days}d ${remainingHours}h (${time.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})})`;
}
// Очистка при размонтировании
onDestroy(() => {
clearInterval(timer);
});
</script>
<div class="timeline-container">
<div class="timeline-controls">
<button
on:click={() => {
currentHour = Math.max(0, currentHour - updateInterval);
dispatchTimeUpdate();
}}
disabled={currentHour <= 0}
class="control-button"
>
← Назад
</button>
<button
on:click={togglePlay}
class="control-button play-button {isPlaying ? 'playing' : ''}"
>
{isPlaying ? '❚❚' : '▶'}
</button>
<button
on:click={() => {
currentHour = Math.min(totalHours, currentHour + updateInterval);
dispatchTimeUpdate();
}}
disabled={currentHour >= totalHours}
class="control-button"
>
Вперёд →
</button>
<div class="time-display">
{formatTime(currentHour)}
</div>
</div>
<input
type="range"
min="0"
max={totalHours}
step={updateInterval}
bind:value={currentHour}
on:input={() => dispatchTimeUpdate()}
class="timeline-slider"
/>
<div class="time-marks">
{#each Array(Math.floor(totalHours / updateInterval) + 1) as _, i}
<div
class="time-mark {currentHour === i * updateInterval ? 'active' : ''}"
on:click={() => {
currentHour = i * updateInterval;
dispatchTimeUpdate();
}}
>
{i * updateInterval % 24 === 0 ? `День ${Math.floor(i * updateInterval / 24) + 1}` : ''}
</div>
{/each}
</div>
</div>
<style>
.timeline-container {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 800px;
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 10px 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 1000;
}
.timeline-controls {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.control-button {
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
transition: all 0.2s;
}
.control-button:hover {
background: #e0e0e0;
}
.control-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.play-button {
width: 40px;
background: #4CAF50;
color: white;
border: none;
}
.play-button.playing {
background: #f44336;
}
.time-display {
font-weight: bold;
min-width: 180px;
text-align: center;
font-family: monospace;
}
.timeline-slider {
width: 100%;
margin: 5px 0;
cursor: pointer;
}
.time-marks {
display: flex;
justify-content: space-between;
margin-top: 5px;
}
.time-mark {
cursor: pointer;
padding: 2px 5px;
font-size: 12px;
position: relative;
}
.time-mark:before {
content: '';
position: absolute;
top: -15px;
left: 50%;
width: 1px;
height: 10px;
background: #ccc;
}
.time-mark.active {
font-weight: bold;
color: #2196F3;
}
.time-mark.active:before {
background: #2196F3;
height: 15px;
}
</style>

View file

@ -5,10 +5,13 @@
import 'leaflet-velocity/dist/leaflet-velocity.css'; import 'leaflet-velocity/dist/leaflet-velocity.css';
import 'leaflet-velocity/dist/leaflet-velocity'; import 'leaflet-velocity/dist/leaflet-velocity';
import 'leaflet.heat'; import 'leaflet.heat';
import 'leaflet-timedimension';
export let map; // принимаем карту из родительского компонента export let map; // принимаем карту из родительского компонента
export let windData; export let windData;
let timeDimension;
let timeDimensionControl;
let velocityLayer; let velocityLayer;
let heatLayer; let heatLayer;
let legend; let legend;
@ -18,6 +21,22 @@
let showVectors = true; let showVectors = true;
let layerControl; let layerControl;
// Преобразование testVelo.json в формат timeData
const prepareTimeData = (windData) => {
if (!windData || windData.length < 2) return {};
// Используем дату из header или текущую дату, если не указана
const refTime = windData[0]?.header?.refTime || new Date().toISOString();
return {
[refTime]: {
u: windData[0].data, // U-компонента (первый объект в массиве)
v: windData[1].data // V-компонента (второй объект)
}
};
};
// Функция для нормализации данных тепловой карты // Функция для нормализации данных тепловой карты
const prepareHeatData = (windData) => { const prepareHeatData = (windData) => {
if (!windData || windData.length < 2) { if (!windData || windData.length < 2) {
@ -55,9 +74,9 @@
// Вычисляем координаты для текущей точки // Вычисляем координаты для текущей точки
const y = Math.floor(i / nx); const y = Math.floor(i / nx);
const x = i % nx; const x = i % nx;
const lat = la1 - y * dy; let lat = la1 - y * dy;
const lng = lo1 + x * dx; let lng = lo1 + x * dx;
if (lng >= 180) lng -= 360;
heatData.push([lat, lng, speed]); heatData.push([lat, lng, speed]);
maxSpeed = Math.max(maxSpeed, speed); maxSpeed = Math.max(maxSpeed, speed);
} }
@ -82,9 +101,9 @@
try { try {
return L.heatLayer(data, { return L.heatLayer(data, {
radius: 20, // Увеличьте радиус для глобальной карты radius: 8, // Увеличьте радиус для глобальной карты
blur: 15, blur: 20,
maxZoom: 10, // maxZoom: 10,
minOpacity: 0.7, minOpacity: 0.7,
gradient: { gradient: {
0.1: 'blue', 0.1: 'blue',
@ -189,7 +208,50 @@
}; };
onMount(() => { onMount(() => {
updateLayers(); if (!map) return;
// 1. Настройка TimeDimension (добавьте эти строки в начале)
// L.TimeDimension.Util.setProxy('https://your-proxy.com/?url='); // Для загрузки больших данных
L.TimeDimension.Util.setCacheLimit(10); // Лимит кэшированных кадров
// 1. Подготовка данных
const timeData = prepareTimeData(windData);
const firstTime = Object.keys(timeData)[0];
// Инициализация TimeDimension
timeDimension = new L.TimeDimension({
period: "PT1H", // Интервал 1 час
timeInterval: '${firstTime}/${firstTime}',
});
// Добавляем контролы времени
timeDimensionControl = new L.Control.TimeDimension({
timeDimension,
position: 'bottomleft',
// autoPlay: true,
playerOptions: {
// transitionTime: 1000,
loop: false,
minBufferReady: -1
}
});
map.addControl(timeDimensionControl);
// 4. Создание слоев
const velocityLayer = L.timeDimension.layer.windVelocity({
displayValues: true,
data: timeData,
displayOptions: {
velocityType: 'Wind Speed',
position: 'bottomleft'
}
}).addTo(map);
// 5. Тепловая карта (адаптируйте под ваш формат)
const heatLayer = L.timeDimension.layer.heat({
radius: 15,
data: prepareTimeHeatData(timeData)
}).addTo(map);
}); });
onDestroy(() => { onDestroy(() => {