Merge branch 'components' of https://git.intra.yksa.space/mikhailov.aa/leaflet_svelte into components
This commit is contained in:
commit
87f0a53cb5
3 changed files with 202 additions and 45 deletions
197
src/routes/WindVisualisation.svelte
Normal file
197
src/routes/WindVisualisation.svelte
Normal 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>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import * as L from "leaflet";
|
import * as L from "leaflet";
|
||||||
import type { Map as LeafletMap, LayerGroup } from "leaflet";
|
import type { Map as LeafletMap, LayerGroup } from "leaflet";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import VelocityLayer from "./velocity.svelte";
|
import WindVisualization from './WindVisualisation.svelte';
|
||||||
import { distHaversine } from "../lib/mathutil.ts";
|
import { distHaversine } from "../lib/mathutil.ts";
|
||||||
import type { PredictionData, TelemetryData } from "../lib/types.ts";
|
import type { PredictionData, TelemetryData } from "../lib/types.ts";
|
||||||
|
|
||||||
|
|
@ -20,15 +20,7 @@
|
||||||
let mouseLng = 0;
|
let mouseLng = 0;
|
||||||
let isSelecting = false;
|
let isSelecting = false;
|
||||||
|
|
||||||
let velocityOptions = {
|
let windData;
|
||||||
displayValues: true,
|
|
||||||
displayOptions: {
|
|
||||||
velocityType: "Global Wind",
|
|
||||||
position: "bottomleft",
|
|
||||||
emptyString: "No velocity data",
|
|
||||||
},
|
|
||||||
data: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();
|
const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();
|
||||||
|
|
||||||
|
|
@ -43,7 +35,7 @@
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
const response = await fetch("src/routes/testVelo.json");
|
const response = await fetch("src/routes/testVelo.json");
|
||||||
velocityOptions.data = await response.json();
|
windData = await response.json();
|
||||||
|
|
||||||
map.on("mousemove", (e: any) => {
|
map.on("mousemove", (e: any) => {
|
||||||
mouseLat = e.latlng.lat;
|
mouseLat = e.latlng.lat;
|
||||||
|
|
@ -152,7 +144,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
{#if map}
|
{#if map && windData}
|
||||||
<VelocityLayer {map} {velocityOptions} />
|
<WindVisualization {map} windData={windData} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -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>
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue