import { createCanvas } from 'canvas'; import axios from 'axios'; class SentinelHubService { constructor() { this.baseUrl = 'https://services.sentinel-hub.com/ogc/wms'; this.instanceId = process.env.SENTINEL_INSTANCE_ID; } async getData(polygon, date = null) { // Генерируем mock данные для демонстрации const bbox = this.getBoundingBox(polygon); return { redBand: this.generateMockBandData(bbox, 0.1, 0.3), nirBand: this.generateMockBandData(bbox, 0.4, 0.8), date: date || new Date().toISOString().split('T')[0], bbox: bbox }; } getBoundingBox(polygon) { const coords = polygon.coordinates[0]; const lats = coords.map(coord => coord[1]); const lons = coords.map(coord => coord[0]); return { minLon: Math.min(...lons), minLat: Math.min(...lats), maxLon: Math.max(...lons), maxLat: Math.max(...lats) }; } generateMockBandData(bbox, min, max) { // Генерируем mock растровые данные const width = 200; const height = 200; const data = []; // Создаем паттерн для более реалистичных данных for (let y = 0; y < height; y++) { const row = []; for (let x = 0; x < width; x++) { // Создаем паттерн с некоторой пространственной корреляцией const baseValue = min + Math.random() * (max - min); // Добавляем некоторые паттерны const pattern1 = Math.sin(x * 0.1) * 0.1; const pattern2 = Math.cos(y * 0.1) * 0.1; const noise = (Math.random() - 0.5) * 0.2; const value = Math.max(min, Math.min(max, baseValue + pattern1 + pattern2 + noise)); row.push(value); } data.push(row); } return { data: data, width: width, height: height, bbox: bbox }; } } const sentinelService = new SentinelHubService(); export async function getSentinelData(polygon, date) { return await sentinelService.getData(polygon, date); } export async function calculateNDVI(satelliteData) { const { redBand, nirBand } = satelliteData; const ndviValues = []; // Создаем canvas для генерации изображения const canvas = createCanvas(redBand.width, redBand.height); const ctx = canvas.getContext('2d'); // Создаем ImageData для манипуляций с пикселями const imageData = ctx.createImageData(redBand.width, redBand.height); for (let y = 0; y < redBand.height; y++) { for (let x = 0; x < redBand.width; x++) { const nir = nirBand.data[y][x]; const red = redBand.data[y][x]; // Calculate NDVI let ndvi = (nir - red) / (nir + red); // Обрабатываем случаи деления на ноль if (isNaN(ndvi) || !isFinite(ndvi)) { ndvi = -1; } ndviValues.push(ndvi); // Convert NDVI to color const color = ndviToColor(ndvi); const index = (y * redBand.width + x) * 4; imageData.data[index] = color[0]; // R imageData.data[index + 1] = color[1]; // G imageData.data[index + 2] = color[2]; // B imageData.data[index + 3] = color[3]; // A } } // Применяем ImageData к canvas ctx.putImageData(imageData, 0, 0); // Конвертируем в base64 const ndviImage = canvas.toDataURL('image/png'); return { ndviValues, ndviImage, width: redBand.width, height: redBand.height, bbox: redBand.bbox }; } function ndviToColor(ndvi) { // Convert NDVI value to RGBA color if (ndvi < -0.2) return [140, 140, 140, 200]; // Gray - water/clouds if (ndvi < 0) return [210, 180, 140, 200]; // Tan - soil if (ndvi < 0.1) return [255, 255, 200, 200]; // Light yellow - very sparse if (ndvi < 0.3) return [255, 255, 0, 200]; // Yellow - sparse vegetation if (ndvi < 0.6) return [0, 255, 0, 200]; // Green - moderate vegetation return [0, 100, 0, 200]; // Dark green - dense vegetation } // Альтернативная функция для генерации градиентного изображения export function generateGradientNDVI(width, height) { const canvas = createCanvas(width, height); const ctx = canvas.getContext('2d'); // Создаем горизонтальный градиент const gradient = ctx.createLinearGradient(0, 0, width, 0); gradient.addColorStop(0.0, '#8C8C8C'); // Вода/Облака gradient.addColorStop(0.2, '#D2B48C'); // Почва gradient.addColorStop(0.4, '#FFFF00'); // Слабая растительность gradient.addColorStop(0.7, '#00FF00'); // Умеренная растительность gradient.addColorStop(1.0, '#006400'); // Густая растительность ctx.fillStyle = gradient; ctx.fillRect(0, 0, width, height); // Добавляем вертикальные вариации const verticalGradient = ctx.createLinearGradient(0, 0, 0, height); verticalGradient.addColorStop(0, 'rgba(255,255,255,0.3)'); verticalGradient.addColorStop(1, 'rgba(0,0,0,0.3)'); ctx.fillStyle = verticalGradient; ctx.fillRect(0, 0, width, height); // Добавляем некоторые паттерны для реалистичности ctx.fillStyle = 'rgba(0,0,0,0.1)'; for (let i = 0; i < 50; i++) { const x = Math.random() * width; const y = Math.random() * height; const size = Math.random() * 10 + 5; ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } return canvas.toDataURL('image/png'); } // Функция для создания паттернов поля export function generateFieldPatternNDVI(width, height) { const canvas = createCanvas(width, height); const ctx = canvas.getContext('2d'); // Заливаем базовым цветом (умеренная растительность) ctx.fillStyle = '#00FF00'; ctx.fillRect(0, 0, width, height); // Добавляем полосы разной растительности const stripeHeight = height / 8; for (let i = 0; i < 8; i++) { const y = i * stripeHeight; if (i % 4 === 0) { // Густая растительность ctx.fillStyle = '#006400'; } else if (i % 4 === 2) { // Слабая растительность ctx.fillStyle = '#FFFF00'; } else { // Умеренная растительность ctx.fillStyle = '#00FF00'; } ctx.fillRect(0, y, width, stripeHeight); } // Добавляем случайные пятна проблемных зон ctx.fillStyle = '#D2B48C'; for (let i = 0; i < 15; i++) { const x = Math.random() * width; const y = Math.random() * height; const size = Math.random() * 20 + 10; ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } // Добавляем водные объекты ctx.fillStyle = '#8C8C8C'; for (let i = 0; i < 3; i++) { const x = Math.random() * width * 0.8; const y = Math.random() * height * 0.8; const w = Math.random() * 30 + 20; const h = Math.random() * 30 + 20; ctx.fillRect(x, y, w, h); } return canvas.toDataURL('image/png'); } export default { getSentinelData, calculateNDVI, generateGradientNDVI, generateFieldPatternNDVI };