GreenPlusbyGXS / web /src /utils /soilHealthAnalyzer.js
gaialive's picture
Upload 106 files
759768a verified
/**
* Soil Health Analyzer
* Comprehensive soil quality assessment and agricultural insights
*/
export class SoilHealthAnalyzer {
constructor() {
this.soilParameters = {
ph: {
optimal: [6.0, 7.5],
acceptable: [5.5, 8.0],
critical: [4.0, 9.0],
unit: '',
name: 'pH Level'
},
nitrogen: {
optimal: [20, 40],
acceptable: [10, 60],
critical: [0, 100],
unit: 'ppm',
name: 'Nitrogen (N)'
},
phosphorus: {
optimal: [30, 60],
acceptable: [15, 80],
critical: [0, 120],
unit: 'ppm',
name: 'Phosphorus (P)'
},
potassium: {
optimal: [150, 300],
acceptable: [100, 400],
critical: [0, 600],
unit: 'ppm',
name: 'Potassium (K)'
},
organicMatter: {
optimal: [3.0, 6.0],
acceptable: [2.0, 8.0],
critical: [0.5, 12.0],
unit: '%',
name: 'Organic Matter'
},
moisture: {
optimal: [40, 60],
acceptable: [25, 75],
critical: [10, 90],
unit: '%',
name: 'Soil Moisture'
},
temperature: {
optimal: [15, 25],
acceptable: [10, 30],
critical: [0, 40],
unit: '°C',
name: 'Soil Temperature'
},
salinity: {
optimal: [0, 2],
acceptable: [0, 4],
critical: [0, 8],
unit: 'dS/m',
name: 'Electrical Conductivity'
}
};
this.cropRequirements = {
tomatoes: { ph: [6.0, 6.8], nitrogen: 'high', phosphorus: 'medium', potassium: 'high' },
corn: { ph: [6.0, 6.8], nitrogen: 'high', phosphorus: 'high', potassium: 'medium' },
wheat: { ph: [6.0, 7.0], nitrogen: 'medium', phosphorus: 'medium', potassium: 'medium' },
soybeans: { ph: [6.0, 7.0], nitrogen: 'low', phosphorus: 'medium', potassium: 'high' },
lettuce: { ph: [6.0, 7.0], nitrogen: 'high', phosphorus: 'medium', potassium: 'high' },
carrots: { ph: [5.5, 6.5], nitrogen: 'medium', phosphorus: 'medium', potassium: 'high' },
potatoes: { ph: [5.0, 6.0], nitrogen: 'medium', phosphorus: 'high', potassium: 'high' },
beans: { ph: [6.0, 7.0], nitrogen: 'low', phosphorus: 'medium', potassium: 'medium' }
};
this.soilTypes = {
clay: { drainage: 'poor', fertility: 'high', workability: 'difficult' },
loam: { drainage: 'good', fertility: 'high', workability: 'excellent' },
sand: { drainage: 'excellent', fertility: 'low', workability: 'easy' },
silt: { drainage: 'moderate', fertility: 'medium', workability: 'good' },
peat: { drainage: 'poor', fertility: 'variable', workability: 'difficult' },
chalk: { drainage: 'good', fertility: 'low', workability: 'good' }
};
}
/**
* Analyze soil health from test data or image
*/
async analyzeSoilHealth(data, location = null, cropType = null) {
try {
console.log('🌱 Starting soil health analysis...');
let soilData;
if (typeof data === 'object' && data.ph !== undefined) {
// Direct test data
soilData = data;
} else if (typeof data === 'string' || data instanceof Blob) {
// Image analysis
soilData = await this.analyzeImageForSoilData(data);
} else {
// Generate mock data for demonstration
soilData = this.generateMockSoilData(location);
}
// Analyze each parameter
const parameterAnalysis = {};
Object.keys(this.soilParameters).forEach(param => {
if (soilData[param] !== undefined) {
parameterAnalysis[param] = this.analyzeParameter(param, soilData[param]);
}
});
// Calculate overall soil health
const overallHealth = this.calculateOverallHealth(parameterAnalysis);
// Generate crop-specific recommendations
const cropRecommendations = cropType ?
this.generateCropRecommendations(parameterAnalysis, cropType) :
this.generateGeneralRecommendations(parameterAnalysis);
// Detect soil type
const soilType = this.detectSoilType(soilData);
// Calculate fertility index
const fertilityIndex = this.calculateFertilityIndex(parameterAnalysis);
// Generate improvement plan
const improvementPlan = this.generateImprovementPlan(parameterAnalysis, soilType);
// Create comprehensive analysis
const analysis = {
timestamp: new Date().toISOString(),
location: location,
cropType: cropType,
soilData: soilData,
parameterAnalysis: parameterAnalysis,
overallHealth: overallHealth,
soilType: soilType,
fertilityIndex: fertilityIndex,
cropRecommendations: cropRecommendations,
improvementPlan: improvementPlan,
seasonalAdvice: this.generateSeasonalAdvice(),
confidence: this.calculateConfidence(soilData),
dataSource: soilData.source || 'Analysis',
nextTestDate: this.calculateNextTestDate()
};
console.log('✅ Soil health analysis completed');
return analysis;
} catch (error) {
console.error('Soil health analysis failed:', error);
throw new Error(`Soil health analysis failed: ${error.message}`);
}
}
/**
* Analyze soil image for basic parameters
*/
async analyzeImageForSoilData(imageSource) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// Analyze image colors and texture
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const soilData = this.extractSoilDataFromImage(imageData);
resolve(soilData);
} catch (error) {
reject(error);
}
};
img.onerror = reject;
img.src = typeof imageSource === 'string' ? imageSource : URL.createObjectURL(imageSource);
});
}
/**
* Extract soil data from image analysis
*/
extractSoilDataFromImage(imageData) {
const data = imageData.data;
let totalR = 0, totalG = 0, totalB = 0;
let pixelCount = 0;
// Calculate average color
for (let i = 0; i < data.length; i += 4) {
totalR += data[i];
totalG += data[i + 1];
totalB += data[i + 2];
pixelCount++;
}
const avgR = totalR / pixelCount;
const avgG = totalG / pixelCount;
const avgB = totalB / pixelCount;
// Estimate soil parameters based on color analysis
// This is a simplified approach - real implementation would use ML models
const darkness = (avgR + avgG + avgB) / 3;
const organicMatter = Math.max(1, 8 - (darkness / 32)); // Darker soil = more organic matter
const redness = avgR - avgG;
const ph = 6.5 + (redness / 50); // Reddish soil tends to be more acidic
const moisture = Math.max(10, 70 - (darkness / 4)); // Darker soil often indicates moisture
return {
ph: Math.max(4.0, Math.min(9.0, ph)),
organicMatter: Math.max(0.5, Math.min(12.0, organicMatter)),
moisture: Math.max(10, Math.min(90, moisture)),
nitrogen: 15 + Math.random() * 20,
phosphorus: 25 + Math.random() * 30,
potassium: 120 + Math.random() * 150,
temperature: 18 + Math.random() * 8,
salinity: Math.random() * 3,
source: 'Image Analysis',
colorProfile: { r: Math.round(avgR), g: Math.round(avgG), b: Math.round(avgB) }
};
}
/**
* Generate mock soil data for demonstration
*/
generateMockSoilData(location = null) {
const baseData = {
ph: 6.5 + (Math.random() - 0.5) * 2,
nitrogen: 20 + Math.random() * 30,
phosphorus: 35 + Math.random() * 40,
potassium: 180 + Math.random() * 200,
organicMatter: 3.5 + Math.random() * 3,
moisture: 45 + Math.random() * 20,
temperature: 18 + Math.random() * 10,
salinity: Math.random() * 4
};
// Adjust based on location if provided
if (location) {
const locationLower = location.toLowerCase();
if (locationLower.includes('desert') || locationLower.includes('arid')) {
baseData.moisture *= 0.3;
baseData.organicMatter *= 0.5;
baseData.salinity *= 2;
} else if (locationLower.includes('forest') || locationLower.includes('woodland')) {
baseData.organicMatter *= 1.5;
baseData.ph -= 0.5; // Forest soils tend to be more acidic
baseData.nitrogen *= 1.3;
} else if (locationLower.includes('agricultural') || locationLower.includes('farm')) {
baseData.nitrogen *= 1.2;
baseData.phosphorus *= 1.1;
baseData.potassium *= 1.1;
}
}
// Add seasonal variation
const month = new Date().getMonth();
if (month >= 5 && month <= 8) { // Summer
baseData.temperature += 5;
baseData.moisture *= 0.8;
} else if (month >= 11 || month <= 2) { // Winter
baseData.temperature -= 8;
baseData.moisture *= 1.2;
}
return {
...baseData,
source: 'Simulated Data',
timestamp: new Date().toISOString()
};
}
/**
* Analyze individual parameter
*/
analyzeParameter(paramName, value) {
const param = this.soilParameters[paramName];
if (!param) return null;
let status, recommendation;
if (value >= param.optimal[0] && value <= param.optimal[1]) {
status = 'optimal';
recommendation = `${param.name} is in the optimal range.`;
} else if (value >= param.acceptable[0] && value <= param.acceptable[1]) {
status = 'acceptable';
if (value < param.optimal[0]) {
recommendation = `${param.name} is slightly low. Consider gradual improvement.`;
} else {
recommendation = `${param.name} is slightly high. Monitor and adjust if needed.`;
}
} else if (value >= param.critical[0] && value <= param.critical[1]) {
status = 'critical';
if (value < param.acceptable[0]) {
recommendation = `${param.name} is critically low. Immediate action required.`;
} else {
recommendation = `${param.name} is critically high. Immediate correction needed.`;
}
} else {
status = 'extreme';
recommendation = `${param.name} is at extreme levels. Professional consultation recommended.`;
}
return {
value: Math.round(value * 100) / 100,
status: status,
recommendation: recommendation,
optimal: param.optimal,
unit: param.unit,
name: param.name
};
}
/**
* Calculate overall soil health
*/
calculateOverallHealth(parameterAnalysis) {
const statusScores = {
optimal: 100,
acceptable: 75,
critical: 40,
extreme: 10
};
let totalScore = 0;
let paramCount = 0;
Object.values(parameterAnalysis).forEach(analysis => {
if (analysis && analysis.status) {
totalScore += statusScores[analysis.status] || 0;
paramCount++;
}
});
const averageScore = paramCount > 0 ? totalScore / paramCount : 0;
let healthCategory;
if (averageScore >= 90) healthCategory = 'Excellent';
else if (averageScore >= 75) healthCategory = 'Good';
else if (averageScore >= 60) healthCategory = 'Fair';
else if (averageScore >= 40) healthCategory = 'Poor';
else healthCategory = 'Critical';
return {
score: Math.round(averageScore),
category: healthCategory,
color: this.getHealthColor(healthCategory)
};
}
getHealthColor(category) {
const colors = {
'Excellent': '#4CAF50',
'Good': '#8BC34A',
'Fair': '#FF9800',
'Poor': '#FF5722',
'Critical': '#F44336'
};
return colors[category] || '#666666';
}
/**
* Detect soil type based on parameters
*/
detectSoilType(soilData) {
// Simplified soil type detection based on available parameters
// Real implementation would use particle size analysis
let soilType = 'loam'; // Default
let confidence = 60;
// Use organic matter and drainage characteristics to estimate
if (soilData.organicMatter > 8) {
soilType = 'peat';
confidence = 70;
} else if (soilData.moisture < 30 && soilData.salinity < 1) {
soilType = 'sand';
confidence = 65;
} else if (soilData.moisture > 70 && soilData.organicMatter < 2) {
soilType = 'clay';
confidence = 65;
} else if (soilData.ph > 7.5 && soilData.salinity < 1) {
soilType = 'chalk';
confidence = 60;
}
return {
type: soilType,
confidence: confidence,
characteristics: this.soilTypes[soilType] || {},
description: this.getSoilTypeDescription(soilType)
};
}
getSoilTypeDescription(soilType) {
const descriptions = {
clay: 'Heavy soil that retains water and nutrients well but can be difficult to work.',
loam: 'Ideal garden soil with good drainage, fertility, and workability.',
sand: 'Light, well-draining soil that warms quickly but requires frequent watering and fertilizing.',
silt: 'Smooth, fertile soil with good water retention but can compact easily.',
peat: 'Organic-rich soil with excellent water retention but may be acidic.',
chalk: 'Alkaline soil that drains well but may lack nutrients and organic matter.'
};
return descriptions[soilType] || 'Mixed soil type with variable characteristics.';
}
/**
* Calculate fertility index
*/
calculateFertilityIndex(parameterAnalysis) {
const keyNutrients = ['nitrogen', 'phosphorus', 'potassium', 'organicMatter'];
let fertilityScore = 0;
let nutrientCount = 0;
keyNutrients.forEach(nutrient => {
const analysis = parameterAnalysis[nutrient];
if (analysis) {
const statusScores = { optimal: 100, acceptable: 75, critical: 40, extreme: 10 };
fertilityScore += statusScores[analysis.status] || 0;
nutrientCount++;
}
});
const index = nutrientCount > 0 ? fertilityScore / nutrientCount : 0;
let category;
if (index >= 90) category = 'Very High';
else if (index >= 75) category = 'High';
else if (index >= 60) category = 'Medium';
else if (index >= 40) category = 'Low';
else category = 'Very Low';
return {
score: Math.round(index),
category: category,
description: this.getFertilityDescription(category)
};
}
getFertilityDescription(category) {
const descriptions = {
'Very High': 'Excellent nutrient levels support vigorous plant growth.',
'High': 'Good nutrient availability for most crops.',
'Medium': 'Adequate nutrients with room for improvement.',
'Low': 'Limited nutrients may restrict plant growth.',
'Very Low': 'Significant nutrient deficiencies require immediate attention.'
};
return descriptions[category] || 'Nutrient status unclear.';
}
/**
* Generate crop-specific recommendations
*/
generateCropRecommendations(parameterAnalysis, cropType) {
const cropReqs = this.cropRequirements[cropType.toLowerCase()];
if (!cropReqs) {
return this.generateGeneralRecommendations(parameterAnalysis);
}
const recommendations = [];
// Check pH requirements
const phAnalysis = parameterAnalysis.ph;
if (phAnalysis) {
const [minPh, maxPh] = cropReqs.ph;
if (phAnalysis.value < minPh) {
recommendations.push({
priority: 'high',
category: 'pH',
issue: `pH too low for ${cropType}`,
action: 'Add lime to raise pH',
target: `${minPh}-${maxPh}`,
timeframe: '2-4 weeks'
});
} else if (phAnalysis.value > maxPh) {
recommendations.push({
priority: 'high',
category: 'pH',
issue: `pH too high for ${cropType}`,
action: 'Add sulfur or organic matter to lower pH',
target: `${minPh}-${maxPh}`,
timeframe: '4-8 weeks'
});
}
}
// Check nutrient requirements
const nutrientReqs = {
nitrogen: cropReqs.nitrogen,
phosphorus: cropReqs.phosphorus,
potassium: cropReqs.potassium
};
Object.keys(nutrientReqs).forEach(nutrient => {
const analysis = parameterAnalysis[nutrient];
const requirement = nutrientReqs[nutrient];
if (analysis && requirement) {
if (requirement === 'high' && analysis.status !== 'optimal') {
recommendations.push({
priority: 'medium',
category: 'nutrition',
issue: `${cropType} requires high ${nutrient}`,
action: this.getNutrientAction(nutrient, 'increase'),
target: 'Optimal range',
timeframe: '1-2 weeks'
});
} else if (requirement === 'low' && analysis.status === 'critical' && analysis.value > analysis.optimal[1]) {
recommendations.push({
priority: 'low',
category: 'nutrition',
issue: `${nutrient} may be too high for ${cropType}`,
action: 'Reduce fertilizer application',
target: 'Moderate levels',
timeframe: '2-4 weeks'
});
}
}
});
return recommendations;
}
/**
* Generate general recommendations
*/
generateGeneralRecommendations(parameterAnalysis) {
const recommendations = [];
Object.keys(parameterAnalysis).forEach(param => {
const analysis = parameterAnalysis[param];
if (analysis && (analysis.status === 'critical' || analysis.status === 'extreme')) {
recommendations.push({
priority: analysis.status === 'extreme' ? 'high' : 'medium',
category: param,
issue: analysis.recommendation,
action: this.getParameterAction(param, analysis),
timeframe: this.getActionTimeframe(param)
});
}
});
return recommendations;
}
getNutrientAction(nutrient, direction) {
const actions = {
nitrogen: {
increase: 'Apply nitrogen-rich fertilizer or compost',
decrease: 'Reduce nitrogen fertilizer, plant nitrogen-fixing crops'
},
phosphorus: {
increase: 'Add bone meal or phosphorus fertilizer',
decrease: 'Avoid phosphorus fertilizers, improve drainage'
},
potassium: {
increase: 'Apply potash or wood ash',
decrease: 'Improve soil drainage, avoid potassium fertilizers'
}
};
return actions[nutrient]?.[direction] || `Adjust ${nutrient} levels`;
}
getParameterAction(param, analysis) {
const actions = {
ph: analysis.value < analysis.optimal[0] ? 'Add lime to raise pH' : 'Add sulfur to lower pH',
organicMatter: 'Add compost, manure, or organic mulch',
moisture: analysis.value < analysis.optimal[0] ? 'Improve irrigation' : 'Improve drainage',
salinity: 'Improve drainage, leach salts with fresh water',
temperature: 'Use mulch to moderate soil temperature'
};
return actions[param] || 'Consult soil specialist';
}
getActionTimeframe(param) {
const timeframes = {
ph: '4-8 weeks',
nitrogen: '1-2 weeks',
phosphorus: '2-4 weeks',
potassium: '2-4 weeks',
organicMatter: '2-6 months',
moisture: '1-2 weeks',
salinity: '4-12 weeks',
temperature: '1-4 weeks'
};
return timeframes[param] || '2-4 weeks';
}
/**
* Generate improvement plan
*/
generateImprovementPlan(parameterAnalysis, soilType) {
const plan = {
immediate: [], // 0-2 weeks
shortTerm: [], // 2-8 weeks
longTerm: [], // 2+ months
seasonal: []
};
// Immediate actions
Object.keys(parameterAnalysis).forEach(param => {
const analysis = parameterAnalysis[param];
if (analysis && analysis.status === 'extreme') {
plan.immediate.push({
action: `Address critical ${param} levels`,
description: analysis.recommendation,
priority: 'critical'
});
}
});
// Short-term actions
if (parameterAnalysis.ph && parameterAnalysis.ph.status !== 'optimal') {
plan.shortTerm.push({
action: 'Adjust soil pH',
description: parameterAnalysis.ph.recommendation,
priority: 'high'
});
}
// Long-term actions
if (parameterAnalysis.organicMatter && parameterAnalysis.organicMatter.status !== 'optimal') {
plan.longTerm.push({
action: 'Improve organic matter content',
description: 'Regular addition of compost and organic materials',
priority: 'medium'
});
}
// Seasonal recommendations
plan.seasonal = this.generateSeasonalAdvice();
return plan;
}
/**
* Generate seasonal advice
*/
generateSeasonalAdvice() {
const month = new Date().getMonth();
const season = month >= 2 && month <= 4 ? 'spring' :
month >= 5 && month <= 7 ? 'summer' :
month >= 8 && month <= 10 ? 'fall' : 'winter';
const advice = {
spring: [
'Test soil before planting season',
'Add compost and organic matter',
'Apply pre-plant fertilizers',
'Improve drainage if needed'
],
summer: [
'Monitor soil moisture regularly',
'Apply mulch to conserve water',
'Side-dress crops with nutrients',
'Watch for nutrient deficiencies'
],
fall: [
'Conduct end-of-season soil test',
'Add lime if pH adjustment needed',
'Plant cover crops',
'Add organic matter for winter decomposition'
],
winter: [
'Plan next year\'s soil improvements',
'Order soil amendments',
'Protect soil from erosion',
'Review and analyze soil test results'
]
};
return {
currentSeason: season,
recommendations: advice[season] || []
};
}
/**
* Calculate confidence in analysis
*/
calculateConfidence(soilData) {
let confidence = 85; // Base confidence
// Increase confidence for more complete data
const parameterCount = Object.keys(soilData).filter(key =>
typeof soilData[key] === 'number' && key !== 'timestamp'
).length;
if (parameterCount >= 6) confidence += 10;
else if (parameterCount >= 4) confidence += 5;
else if (parameterCount < 3) confidence -= 15;
// Adjust based on data source
if (soilData.source === 'Lab Analysis') confidence += 10;
else if (soilData.source === 'Image Analysis') confidence -= 10;
return Math.max(60, Math.min(98, confidence));
}
/**
* Calculate next test date
*/
calculateNextTestDate() {
const nextTest = new Date();
nextTest.setMonth(nextTest.getMonth() + 6); // Test every 6 months
return nextTest.toISOString().split('T')[0];
}
}
// Create singleton instance
export const soilHealthAnalyzer = new SoilHealthAnalyzer();
// Export main analysis function
export const analyzeSoilHealth = async (data, location, cropType) => {
try {
return await soilHealthAnalyzer.analyzeSoilHealth(data, location, cropType);
} catch (error) {
console.error('Soil health analysis error:', error);
throw error;
}
};
export default soilHealthAnalyzer;