This commit is contained in:
ThePetrovich 2025-06-27 21:15:30 +08:00
commit 87f0a53cb5
3 changed files with 202 additions and 45 deletions

View file

@ -0,0 +1,197 @@
<script>
import { onMount, onDestroy } from 'svelte';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet-velocity/dist/leaflet-velocity.css';
import 'leaflet-velocity/dist/leaflet-velocity';
import 'leaflet.heat';
export let map; // принимаем карту из родительского компонента
export let windData;
let velocityLayer;
let heatLayer;
let legend;
// Состояние переключателей
let showHeatmap = true;
let showVectors = true;
let layerControl;
// Функция для нормализации данных тепловой карты
const prepareHeatData = (windData) => {
if (!windData || !windData.header || !windData.data) {
console.warn("Wind data is missing or incomplete");
return [];
}
const { lo1, la1, dx, dy, nx, ny } = windData.header;
const heatData = [];
let maxSpeed = 0;
// Собираем данные и находим максимальную скорость
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);
if (!isNaN(speed)) {
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}');
// Нормализуем значения интенсивности от 0 до 1
if (maxSpeed > 0) {
return heatData.map(([lat, lng, intensity]) => [lat, lng, intensity / maxSpeed]);
}
return heatData;
};
// Создание тепловой карты
const createHeatLayer = (data) => {
if (!data || data.length === 0) {
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'
}
});
} catch (e) {
console.error("Failed to create heat layer:", e);
return null;
}
};
// Обновление слоев
const updateLayers = () => {
if (!map || !windData) return;
// Удаляем старые слои
if (velocityLayer) map.removeLayer(velocityLayer);
if (heatLayer) map.removeLayer(heatLayer);
if (legend) map.removeControl(legend);
// Создаем слой векторов ветра
velocityLayer = L.velocityLayer({
displayValues: true,
displayOptions: {
velocityType: 'Wind Speed',
position: 'bottomleft',
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");
}
};
// Создание легенды с учетом максимальной скорости
const createLegend = (maxSpeed) => {
if (!map) return;
legend = L.control({ position: 'bottomright' });
legend.onAdd = () => {
const div = L.DomUtil.create('div', 'wind-heat-legend');
div.innerHTML = `
<h4>Wind Speed (m/s)</h4>
<div class="legend-scale">
<div class="legend-color" style="background: #0000FF;"></div>
<div class="legend-color" style="background: #00FFFF;"></div>
<div class="legend-color" style="background: #00FF00;"></div>
<div class="legend-color" style="background: #FFFF00;"></div>
<div class="legend-color" style="background: #FF0000;"></div>
</div>
<div class="legend-labels">
<span>0</span>
<span>${(maxSpeed * 0.25).toFixed(1)}</span>
<span>${(maxSpeed * 0.5).toFixed(1)}</span>
<span>${(maxSpeed * 0.75).toFixed(1)}</span>
<span>${maxSpeed.toFixed(1)}</span>
</div>
`;
return div;
};
legend.addTo(map);
};
onMount(() => {
updateLayers();
});
onDestroy(() => {
if (map) {
if (velocityLayer) map.removeLayer(velocityLayer);
if (heatLayer) map.removeLayer(heatLayer);
if (legend) map.removeControl(legend);
}
});
// Реактивность на изменение параметров
$: if (map && windData) {
updateLayers();
};
</script>
<style>
.wind-heat-legend {
padding: 8px 10px;
background: rgba(255, 255, 255, 0.9);
border-radius: 5px;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
line-height: 1.2;
color: #333;
font-family: Arial, sans-serif;
}
.wind-heat-legend h4 {
margin: 0 0 5px;
font-size: 14px;
font-weight: bold;
}
.legend-scale {
display: flex;
margin-bottom: 3px;
}
.legend-color {
height: 12px;
flex-grow: 1;
}
.legend-labels {
display: flex;
justify-content: space-between;
font-size: 11px;
}
</style>

View file

@ -3,7 +3,7 @@
import * as L from "leaflet";
import type { Map as LeafletMap, LayerGroup } from "leaflet";
import "leaflet/dist/leaflet.css";
import VelocityLayer from "./velocity.svelte";
import WindVisualization from './WindVisualisation.svelte';
import { distHaversine } from "../lib/mathutil.ts";
import type { PredictionData, TelemetryData } from "../lib/types.ts";
@ -20,15 +20,7 @@
let mouseLng = 0;
let isSelecting = false;
let velocityOptions = {
displayValues: true,
displayOptions: {
velocityType: "Global Wind",
position: "bottomleft",
emptyString: "No velocity data",
},
data: null,
};
let windData;
const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();
@ -43,7 +35,7 @@
}).addTo(map);
const response = await fetch("src/routes/testVelo.json");
velocityOptions.data = await response.json();
windData = await response.json();
map.on("mousemove", (e: any) => {
mouseLat = e.latlng.lat;
@ -152,7 +144,7 @@
</p>
</div>
<slot />
{#if map}
<VelocityLayer {map} {velocityOptions} />
{#if map && windData}
<WindVisualization {map} windData={windData} />
{/if}
</div>

View file

@ -1,32 +0,0 @@
<script>
import { onMount, onDestroy } from 'svelte';
import L from 'leaflet';
import 'leaflet-velocity/dist/leaflet-velocity.css';
import 'leaflet-velocity/dist/leaflet-velocity';
export let map; // принимаем карту из родительского компонента
export let velocityOptions = {}; // параметры для velocity
let velocityLayer;
onMount(() => {
if (map && !velocityLayer) {
// Создаем слой velocity
velocityLayer = L.velocityLayer(velocityOptions);
// Добавляем слой на карту
velocityLayer.addTo(map);
}
});
onDestroy(() => {
if (map && velocityLayer) {
map.removeLayer(velocityLayer);
}
});
// Реактивность на изменение параметров
$: if (velocityLayer && velocityOptions) {
velocityLayer.setOptions(velocityOptions);
}
</script>