Files
vector/frontend/src/components/SearchMap.jsx
T
2026-06-06 18:31:55 +00:00

205 lines
6.5 KiB
React

import React from 'react';
import { MapContainer, TileLayer, Marker, Popup, Polygon } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
// Fix default marker icon issue with webpack
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
});
const SearchMap = ({ tnpLat, tnpLon, maxDistance, zones }) => {
const tnpPosition = [tnpLat || 53.9, tnpLon || 27.56];
const distance = maxDistance || 5.0;
// Функция для создания точек сектора
const createSectorPoints = (centerLat, centerLon, radiusKm, startAngle, endAngle, points = 20) => {
const coords = [[centerLat, centerLon]]; // Начинаем с центра
// Конвертируем км в градусы (приблизительно)
const latPerKm = 1 / 111.0;
const lonPerKm = 1 / (111.0 * Math.cos(centerLat * Math.PI / 180));
// Создаем точки дуги
for (let i = 0; i <= points; i++) {
const angle = startAngle + (endAngle - startAngle) * (i / points);
const angleRad = angle * Math.PI / 180;
const lat = centerLat + radiusKm * latPerKm * Math.cos(angleRad);
const lon = centerLon + radiusKm * lonPerKm * Math.sin(angleRad);
coords.push([lat, lon]);
}
coords.push([centerLat, centerLon]); // Замыкаем в центре
return coords;
};
// Направления и их углы
const directionAngles = {
'N': 0,
'NE': 45,
'E': 90,
'SE': 135,
'S': 180,
'SW': 225,
'W': 270,
'NW': 315
};
// Создаем секторы на основе зон
const sectors = zones && zones.length > 0 ? zones.slice(0, 8).map((zone, idx) => {
const angle = directionAngles[zone.direction] || 0;
const startAngle = angle - 22.5; // ±22.5° = 45° сектор
const endAngle = angle + 22.5;
// Определяем радиус на основе score
let radius = distance;
if (zone.score >= 90) radius = distance * 1.0;
else if (zone.score >= 70) radius = distance * 0.8;
else if (zone.score >= 50) radius = distance * 0.6;
else radius = distance * 0.4;
// Цвет на основе приоритета
let color, fillOpacity;
if (idx === 0) {
color = '#ff4d00';
fillOpacity = 0.35;
} else if (idx === 1) {
color = '#ff9f1a';
fillOpacity = 0.25;
} else if (idx === 2) {
color = '#ffd166';
fillOpacity = 0.2;
} else {
color = '#8b949e';
fillOpacity = 0.1;
}
return {
points: createSectorPoints(tnpPosition[0], tnpPosition[1], radius, startAngle, endAngle),
color,
fillOpacity,
zone
};
}) : [];
// Если зон нет, создаем базовые концентрические круги
const createCirclePoints = (centerLat, centerLon, radiusKm, points = 40) => {
const coords = [];
const latPerKm = 1 / 111.0;
const lonPerKm = 1 / (111.0 * Math.cos(centerLat * Math.PI / 180));
for (let i = 0; i <= points; i++) {
const angle = (i / points) * 2 * Math.PI;
const lat = centerLat + radiusKm * latPerKm * Math.cos(angle);
const lon = centerLon + radiusKm * lonPerKm * Math.sin(angle);
coords.push([lat, lon]);
}
return coords;
};
const defaultZones = [
{
points: createCirclePoints(tnpPosition[0], tnpPosition[1], distance),
color: '#ffd166',
fillOpacity: 0.15,
label: `Зона 3 (${distance.toFixed(1)} км)`,
probability: 10
},
{
points: createCirclePoints(tnpPosition[0], tnpPosition[1], distance * 0.75),
color: '#ff9f1a',
fillOpacity: 0.2,
label: `Зона 2 (${(distance * 0.75).toFixed(1)} км)`,
probability: 20
},
{
points: createCirclePoints(tnpPosition[0], tnpPosition[1], distance * 0.5),
color: '#ff4d00',
fillOpacity: 0.3,
label: `Зона 1 (${(distance * 0.5).toFixed(1)} км)`,
probability: 70
}
];
return (
<MapContainer
center={tnpPosition}
zoom={12}
style={{ width: '100%', height: '500px', borderRadius: '8px' }}
>
<TileLayer
attribution=""
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* Рисуем секторы или круги */}
{sectors.length > 0 ? (
sectors.map((sector, idx) => (
<Polygon
key={idx}
positions={sector.points}
pathOptions={{
color: sector.color,
fillColor: sector.color,
fillOpacity: sector.fillOpacity,
weight: 2
}}
>
<Popup>
<strong>#{idx + 1} {sector.zone.name}</strong><br/>
Направление: {sector.zone.direction}<br/>
Приоритет: {sector.zone.score}%<br/>
{sector.zone.reasoning}
</Popup>
</Polygon>
))
) : (
defaultZones.map((zone, idx) => (
<Polygon
key={idx}
positions={zone.points}
pathOptions={{
color: zone.color,
fillColor: zone.color,
fillOpacity: zone.fillOpacity,
weight: 2
}}
>
<Popup>
<strong>{zone.label}</strong><br/>
Вероятность: {zone.probability}%
</Popup>
</Polygon>
))
)}
{/* ТНП - красный маркер */}
<Marker
position={tnpPosition}
icon={L.icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})}
>
<Popup>
<strong>ТНП - Точка начала поиска</strong><br/>
Координаты: {tnpLat?.toFixed(6)}, {tnpLon?.toFixed(6)}<br/>
Максимальная дистанция: {distance.toFixed(1)} км
</Popup>
</Marker>
</MapContainer>
);
};
export default SearchMap;