Import Vector lab project

This commit is contained in:
root
2026-06-06 18:31:55 +00:00
commit fcc22063ed
105 changed files with 31639 additions and 0 deletions
@@ -0,0 +1,252 @@
import React, { useState, useEffect, useRef } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet.heat';
import './HeatmapView.css';
const HeatmapView = () => {
const mapRef = useRef(null);
const mapInstanceRef = useRef(null);
const heatLayerRef = useRef(null);
const [heatmapData, setHeatmapData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [total, setTotal] = useState(0);
// Filters
const [filterType, setFilterType] = useState('all');
const [ageGroup, setAgeGroup] = useState('');
const [season, setSeason] = useState('');
const [yearFrom, setYearFrom] = useState('');
const [yearTo, setYearTo] = useState('');
// Layer visibility
const [showHeatmap, setShowHeatmap] = useState(true);
// Initialize map
useEffect(() => {
if (!mapRef.current || mapInstanceRef.current) return;
// Create map centered on Russia
const map = L.map(mapRef.current).setView([55.7558, 37.6173], 5);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 18
}).addTo(map);
mapInstanceRef.current = map;
return () => {
if (mapInstanceRef.current) {
mapInstanceRef.current.remove();
mapInstanceRef.current = null;
}
};
}, []);
// Fetch heatmap data
const fetchHeatmapData = async () => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams({
type: filterType
});
if (filterType === 'age' && ageGroup) {
params.append('age_group', ageGroup);
}
if (filterType === 'season' && season) {
params.append('season', season);
}
if (yearFrom) {
params.append('year_from', yearFrom);
}
if (yearTo) {
params.append('year_to', yearTo);
}
const response = await fetch(`http://192.168.0.99:8000/api/v1/stats/heatmap?${params}`);
if (!response.ok) {
throw new Error('Failed to fetch heatmap data');
}
const data = await response.json();
setHeatmapData(data.points);
setTotal(data.total);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
// Update heatmap layer
useEffect(() => {
if (!mapInstanceRef.current) return;
// Remove existing heat layer
if (heatLayerRef.current) {
mapInstanceRef.current.removeLayer(heatLayerRef.current);
heatLayerRef.current = null;
}
// Add new heat layer if visible and data exists
if (showHeatmap && heatmapData.length > 0) {
const heatData = heatmapData.map(point => [point.lat, point.lon, point.intensity]);
const heatLayer = L.heatLayer(heatData, {
radius: 25,
blur: 15,
maxZoom: 10,
max: 1.0,
gradient: {
0.0: 'blue',
0.5: 'lime',
0.7: 'yellow',
1.0: 'red'
}
});
heatLayer.addTo(mapInstanceRef.current);
heatLayerRef.current = heatLayer;
}
}, [heatmapData, showHeatmap]);
// Load data on mount and filter change
useEffect(() => {
fetchHeatmapData();
}, [filterType, ageGroup, season, yearFrom, yearTo]);
const handleFilterTypeChange = (type) => {
setFilterType(type);
// Reset specific filters
if (type !== 'age') setAgeGroup('');
if (type !== 'season') setSeason('');
};
return (
<div className="heatmap-view">
<div className="heatmap-controls">
<h2>Тепловая карта находок</h2>
<div className="control-group">
<label>Тип фильтра:</label>
<div className="button-group">
<button
className={filterType === 'all' ? 'active' : ''}
onClick={() => handleFilterTypeChange('all')}
>
Все
</button>
<button
className={filterType === 'age' ? 'active' : ''}
onClick={() => handleFilterTypeChange('age')}
>
По возрасту
</button>
<button
className={filterType === 'season' ? 'active' : ''}
onClick={() => handleFilterTypeChange('season')}
>
По сезону
</button>
<button
className={filterType === 'outcome' ? 'active' : ''}
onClick={() => handleFilterTypeChange('outcome')}
>
По исходу
</button>
</div>
</div>
{filterType === 'age' && (
<div className="control-group">
<label>Возрастная группа:</label>
<select value={ageGroup} onChange={(e) => setAgeGroup(e.target.value)}>
<option value="">Все</option>
<option value="0-6">0-6 лет</option>
<option value="7-10">7-10 лет</option>
<option value="11-14">11-14 лет</option>
<option value="15-17">15-17 лет</option>
<option value="18+">18+ лет</option>
</select>
</div>
)}
{filterType === 'season' && (
<div className="control-group">
<label>Сезон:</label>
<select value={season} onChange={(e) => setSeason(e.target.value)}>
<option value="">Все</option>
<option value="зима">Зима</option>
<option value="весна">Весна</option>
<option value="лето">Лето</option>
<option value="осень">Осень</option>
</select>
</div>
)}
<div className="control-group">
<label>Период:</label>
<div className="year-range">
<input
type="number"
placeholder="От года"
value={yearFrom}
onChange={(e) => setYearFrom(e.target.value)}
min="2000"
max="2100"
/>
<span>—</span>
<input
type="number"
placeholder="До года"
value={yearTo}
onChange={(e) => setYearTo(e.target.value)}
min="2000"
max="2100"
/>
</div>
</div>
<div className="control-group">
<label>
<input
type="checkbox"
checked={showHeatmap}
onChange={(e) => setShowHeatmap(e.target.checked)}
/>
Показать тепловую карту
</label>
</div>
<div className="stats-info">
<p>Всего точек: <strong>{total}</strong></p>
{loading && <p className="loading">Загрузка...</p>}
{error && <p className="error">Ошибка: {error}</p>}
</div>
<div className="legend">
<h3>Легенда</h3>
<div className="legend-gradient">
<span className="legend-label">Низкая плотность</span>
<div className="gradient-bar"></div>
<span className="legend-label">Высокая плотность</span>
</div>
</div>
</div>
<div className="map-container">
<div ref={mapRef} className="map"></div>
</div>
</div>
);
};
export default HeatmapView;