Import Vector lab project
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user