Merge remote-tracking branch 'origin/velocity' into components

This commit is contained in:
ThePetrovich 2025-07-02 19:17:45 +08:00
commit 0e4d5a8d47
5 changed files with 365 additions and 57 deletions

View file

@ -25,7 +25,7 @@
onMount(async () => {
if (!mapContainer) return;
map = L.map(mapContainer, { zoomControl: false }).setView([51.505, -0.09], 13);
map = L.map(mapContainer, { zoomControl: false }).setView([51.505, -0.09], 13);
L.control.zoom({ position: "bottomleft" }).addTo(map);
plotLayerGroup = L.layerGroup().addTo(map);

View file

@ -20,36 +20,54 @@
// Функция для нормализации данных тепловой карты
const prepareHeatData = (windData) => {
if (!windData || !windData.header || !windData.data) {
console.warn("Wind data is missing or incomplete");
return [];
if (!windData || windData.length < 2) {
console.warn("Invalid wind data structure");
return [];
}
const { lo1, la1, dx, dy, nx, ny } = windData.header;
// Получаем U и V компоненты
const uComponent = windData.find(item => item.header.parameterNumber === 2);
const vComponent = windData.find(item => item.header.parameterNumber === 3);
if (!uComponent || !vComponent) {
console.warn("Missing wind components");
return [];
}
const header = uComponent.header; // Используем header из U компоненты
const { lo1, la1, dx, dy, nx, ny } = header;
const heatData = [];
let maxSpeed = 0;
// Проверяем совпадение размеров данных
if (uComponent.data.length !== vComponent.data.length) {
console.warn("U and V components have different lengths");
return [];
}
// Собираем данные и находим максимальную скорость
for (let y = 0; y < ny; y++) {
for (let x = 0; x < nx; x++) {
const u = windData.data[y][x * 2];
const v = windData.data[y][x * 2 + 1];
const speed = Math.sqrt(u * u + v * v);
for (let i = 0; i < uComponent.data.length; i++) {
const u = uComponent.data[i];
const v = vComponent.data[i];
const speed = Math.sqrt(u * u + v * v);
if (!isNaN(speed)) {
const lat = la1 - y * dy;
const lng = lo1 + x * dx;
heatData.push([lat, lng, speed]);
maxSpeed = Math.max(maxSpeed, speed);
}
if (!isNaN(speed)) {
// Вычисляем координаты для текущей точки
const y = Math.floor(i / nx);
const x = i % nx;
const lat = la1 - y * dy;
const lng = lo1 + x * dx;
heatData.push([lat, lng, speed]);
maxSpeed = Math.max(maxSpeed, speed);
}
}
console.log('Prepared heat data: ${heatData.length} points, max speed: ${maxSpeed}');
console.log(`Prepared heat data: ${heatData.length} points, max speed: ${maxSpeed}`);
// Нормализуем значения интенсивности от 0 до 1
if (maxSpeed > 0) {
return heatData.map(([lat, lng, intensity]) => [lat, lng, intensity / maxSpeed]);
return heatData.map(([lat, lng, intensity]) => [lat, lng, intensity / maxSpeed]);
}
return heatData;
@ -58,27 +76,27 @@
// Создание тепловой карты
const createHeatLayer = (data) => {
if (!data || data.length === 0) {
console.warn("No valid heat data provided");
return null;
console.warn("No valid heat data provided");
return null;
}
try {
return L.heatLayer(data, {
radius: 15,
blur: 20,
maxZoom: 17,
minOpacity: 0.5,
gradient: {
0.1: 'blue',
0.3: 'cyan',
0.5: 'lime',
0.7: 'yellow',
1.0: 'red'
}
});
return L.heatLayer(data, {
radius: 20, // Увеличьте радиус для глобальной карты
blur: 15,
maxZoom: 10,
minOpacity: 0.7,
gradient: {
0.1: 'blue',
0.3: 'cyan',
0.5: 'lime',
0.7: 'yellow',
1.0: 'red'
}
});
} catch (e) {
console.error("Failed to create heat layer:", e);
return null;
console.error("Failed to create heat layer:", e);
return null;
}
};
@ -92,28 +110,53 @@
if (legend) map.removeControl(legend);
// Создаем слой векторов ветра
velocityLayer = L.velocityLayer({
displayValues: true,
displayOptions: {
velocityType: 'Wind Speed',
position: 'bottomright',
emptyString: 'No wind data',
},
data: windData
}).addTo(map);
if (showVectors) {
velocityLayer = L.velocityLayer({
displayValues: true,
displayOptions: {
velocityType: 'Wind Speed',
position: 'bottomright',
emptyString: 'No wind data',
},
data: windData
}).addTo(map);
}
// Создаем тепловую карту
const heatData = prepareHeatData(windData);
heatLayer = createHeatLayer(heatData);
if (heatLayer) {
heatLayer.addTo(map);
createLegend(Math.max(...heatData.map(point => point[2])));
} else {
console.warn("Heat layer was not created");
if (showHeatmap) {
const heatData = prepareHeatData(windData);
heatLayer = createHeatLayer(heatData);
if (heatLayer) {
heatLayer.addTo(map);
createLegend(Math.max(...heatData.map(point => point[2])));
}
}
// Обновляем контроль слоев
updateLayerControl();
};
const updateLayerControl = () => {
if (layerControl) {
map.removeControl(layerControl);
}
const overlays = {};
if (velocityLayer) {
overlays['Векторы ветра'] = velocityLayer;
}
if (heatLayer) {
overlays['Тепловая карта'] = heatLayer;
}
layerControl = L.control.layers(null, overlays, {
collapsed: false,
position: 'topright'
}).addTo(map);
};
// Создание легенды с учетом максимальной скорости
const createLegend = (maxSpeed) => {
if (!map) return;
@ -162,8 +205,42 @@
updateLayers();
};
</script>
<div class="layer-controls">
<div class="control-group">
<label>
<input type="checkbox" bind:checked={showHeatmap}> Тепловая карта
</label>
<label>
<input type="checkbox" bind:checked={showVectors}> Векторы ветра
</label>
</div>
</div>
<style>
.wind-heat-legend {
.layer-controls {
position: absolute;
bottom: 30px;
left: 10px;
z-index: 1000;
background: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.control-group label {
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
cursor: pointer;
}
:global(.wind-heat-legend) {
padding: 8px 10px;
background: rgba(255, 255, 255, 0.9);
border-radius: 5px;
@ -173,23 +250,23 @@
font-family: Arial, sans-serif;
}
.wind-heat-legend h4 {
:global(.wind-heat-legend h4) {
margin: 0 0 5px;
font-size: 14px;
font-weight: bold;
}
.legend-scale {
:global(legend-scale) {
display: flex;
margin-bottom: 3px;
}
.legend-color {
:global(legend-color) {
height: 12px;
flex-grow: 1;
}
.legend-labels {
:global(.legend-labels) {
display: flex;
justify-content: space-between;
font-size: 11px;