You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
7.4 KiB
231 lines
7.4 KiB
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
|
|
}; |