[go: nahoru, domu]

Skip to content

Commit

Permalink
Revamped predict crop yield and farm monitoring and refactored logic
Browse files Browse the repository at this point in the history
  • Loading branch information
MuindiStephen committed Aug 23, 2024
1 parent 1e25bd3 commit 3e8d183
Show file tree
Hide file tree
Showing 9 changed files with 560 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ data class FarmConditions(
val windspeed: Double,
val precipitation: Double,
val lightDensity: Double,
val nbkLevel: Double
val nbkLevel: Double,
val soilPh: Double
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
Expand All @@ -25,10 +26,14 @@ import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.gms.maps.model.MarkerOptions
import com.steve_md.smartmkulima.R
import com.steve_md.smartmkulima.adapter.AgrodealerAdapter
import com.steve_md.smartmkulima.model.AgroDealer
import com.steve_md.smartmkulima.ui.fragments.others.LocationProvider
import com.steve_md.smartmkulima.utils.displaySnackBar
import com.steve_md.smartmkulima.utils.isInternetAvailable
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber

Expand All @@ -50,6 +55,7 @@ class LocateAgriTechCompaniesFragment : Fragment() , OnMapReadyCallback {
private lateinit var mapView: MapView
private lateinit var googleMap: GoogleMap
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationProvider: LocationProvider


companion object {
Expand Down Expand Up @@ -145,6 +151,13 @@ class LocateAgriTechCompaniesFragment : Fragment() , OnMapReadyCallback {
override fun onMapReady(map: GoogleMap) {
googleMap = map

if (!isInternetAvailable(requireContext())) {
showInternetErrorDialog()
return
} else {
displaySnackBar("Agrodealers location updated successfully")
}

/**
* If permissions have been already granted
*/
Expand Down Expand Up @@ -173,12 +186,15 @@ class LocateAgriTechCompaniesFragment : Fragment() , OnMapReadyCallback {
promptUserForLocationPermissions()
return
}
fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
location?.let {
val userLatLng = LatLng(it.latitude, it.longitude)
//fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
// location?.let {

locationProvider = LocationProvider(this.requireContext())
locationProvider.getLastKnownLocation { location ->
val userLatLng = location?.let { LatLng(it.latitude, location.longitude) }
val agrodealers = getAgroDealersData().filter { agrovet ->
val agrovetLatLng = LatLng(agrovet.latitude, agrovet.longitude)
calculateDistance(userLatLng, agrovetLatLng) <= SEARCH_RADIUS_METERS
userLatLng?.let { calculateDistance(it, agrovetLatLng) }!! <= SEARCH_RADIUS_METERS
}

// Add filtered Agro-Dealers Markers to the map
Expand All @@ -189,16 +205,21 @@ class LocateAgriTechCompaniesFragment : Fragment() , OnMapReadyCallback {
.position(location2)
.title(agrodealer.name)
)?.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))

}

googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(userLatLng, 18f))
userLatLng?.let { CameraUpdateFactory.newLatLngZoom(it, 18f) }
?.let { googleMap.moveCamera(it)
googleMap.animateCamera(it)}

if (agrodealers.isNotEmpty()) {

val userLatLng1 = LatLng(it.latitude, it.longitude)
val boundsBuilder = LatLngBounds.Builder()

val userLatLng1 = location?.let { LatLng(it.latitude, location.longitude) }
val agrodealers1 = getAgroDealersData().filter { agrovet ->
val agrovetLatLng = LatLng(agrovet.latitude, agrovet.longitude)
calculateDistance(userLatLng1, agrovetLatLng) <= SEARCH_RADIUS_METERS
userLatLng1?.let { calculateDistance(it, agrovetLatLng) }!! <= SEARCH_RADIUS_METERS
}

// googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(userLatLng, 18f))
Expand All @@ -212,16 +233,33 @@ class LocateAgriTechCompaniesFragment : Fragment() , OnMapReadyCallback {
.position(location1)
.title(agrodealer.name)
)?.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
boundsBuilder.include(location1)
}

googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(userLatLng1, 18f))

userLatLng1?.let { CameraUpdateFactory.newLatLngZoom(it, 18f) }
?.let { googleMap.moveCamera(it) }

// Include the user's location in the bounds
userLatLng?.let { boundsBuilder.include(it) }

/**
* Adjust the map's camera zoom level after calculating the bounds that
* include all agro-dealers within a 50 km radius.
* using the `LatLngBounds` .
*/
val bounds = boundsBuilder.build()
val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, 100) // 100 is padding
googleMap.moveCamera(cameraUpdate)
googleMap.animateCamera(cameraUpdate)

}

if(agrodealers.isEmpty()) {
view?.findViewById<TextView>(R.id.textViewAgrodealersNotavailable)?.visibility = View.VISIBLE
view?.findViewById<LottieAnimationView>(R.id.LottieNoRecords)?.visibility = View.VISIBLE
}
}
// }
}
}

Expand Down Expand Up @@ -378,4 +416,19 @@ class LocateAgriTechCompaniesFragment : Fragment() , OnMapReadyCallback {
mapView.onLowMemory()
}

private fun showInternetErrorDialog() {
AlertDialog.Builder(requireContext())
.setTitle("No Internet Connection to load map")
.setMessage("Please check your internet connection and try again.")
.setPositiveButton("Retry") { dialog, _ ->
dialog.dismiss()
// Retry the map load or any other action
mapView.getMapAsync(this@LocateAgriTechCompaniesFragment)
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.view.isVisible
Expand All @@ -28,6 +29,7 @@ import com.steve_md.smartmkulima.R
import com.steve_md.smartmkulima.model.FarmConditions
import com.steve_md.smartmkulima.ui.fragments.others.LocationProvider
import com.steve_md.smartmkulima.utils.displaySnackBar
import com.steve_md.smartmkulima.utils.isInternetAvailable
import dagger.hilt.android.AndroidEntryPoint
import ir.mahozad.android.PieChart
import ir.mahozad.android.unit.Dimension
Expand Down Expand Up @@ -73,9 +75,7 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {

if (location != null) {
loading.isVisible = false
monitorFarmConditions(location.latitude, location.longitude)
displaySnackBar("Location Update successful.")
} else {
monitorFarmConditions(location.latitude, location.longitude) } else {
// Handle case when location is null
loading.isVisible = false
Timber.e("An error occurred")
Expand All @@ -90,15 +90,16 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {
val pieChart = view?.findViewById<PieChart>(R.id.pieChart)

pieChart?.slices = listOf(
PieChart.Slice(0.3f, Color.rgb(120, 181, 0), Color.rgb(149, 224, 0), legend = "Temperature"),
PieChart.Slice(0.2f, Color.rgb(204, 168, 0), Color.rgb(249, 228, 0), legend = "Humidity"),
PieChart.Slice(0.2f, Color.rgb(0, 162, 216), Color.rgb(31, 199, 255), legend = "Soil Moisture"),
PieChart.Slice(0.30f, Color.rgb(120, 181, 0), Color.rgb(149, 224, 0), legend = "Temperature"),
PieChart.Slice(0.20f, Color.rgb(204, 168, 0), Color.rgb(249, 228, 0), legend = "Humidity"),
PieChart.Slice(0.20f, Color.rgb(0, 162, 216), Color.rgb(31, 199, 255), legend = "Soil Moisture"),
PieChart.Slice(0.17f, Color.rgb(255, 4, 4), Color.rgb(255, 72, 86), legend = "Wind speed"),
PieChart.Slice(0.13f, Color.rgb(160, 165, 170), Color.rgb(175, 180, 185), legend = "Precipitation") ,
PieChart.Slice(0.13f, Color.rgb(160, 165, 170), Color.rgb(175, 180, 185), legend = "Light Density"),
PieChart.Slice(0.13f, Color.rgb(160, 165, 170), Color.rgb(175, 180, 185), legend = "NBK level")
PieChart.Slice(0.13f, Color.rgb(160, 100, 167), Color.rgb(160, 145, 175), legend = "Precipitation") ,
PieChart.Slice(0.23f, Color.rgb(0, 163, 170), Color.rgb(165, 180, 165), legend = "Light Density"),
PieChart.Slice(0.25f, Color.rgb(110, 165, 169), Color.rgb(175, 130, 185), legend = "NBK level")
)


pieChart?.gradientType = PieChart.GradientType.RADIAL
pieChart?.legendIconsMargin = Dimension.DP(8F)
pieChart?.legendTitleMargin = Dimension.DP(14F)
Expand All @@ -108,6 +109,15 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {
pieChart?.legendsSize = Dimension.DP(11F)
pieChart?.legendsPercentageSize = Dimension.DP(11F)
pieChart?.legendsIcon = PieChart.DefaultIcons.SQUARE
pieChart?.legendsTitle = "Farm conditions"
pieChart?.legendPosition = PieChart.LegendPosition.BOTTOM
pieChart?.labelType = PieChart.LabelType.NONE
pieChart?.isLegendEnabled = true
pieChart?.legendsTitleColor = Color.BLACK
pieChart?.legendsColor = Color.BLACK

// pieChart?.isAnimationEnabled = true
// pieChart?.isLegendsPercentageEnabled = true
}

val Float.toSp get() = this * Resources.getSystem().displayMetrics.scaledDensity
Expand All @@ -124,7 +134,8 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {
windspeed = 9.0,
precipitation = 5.8,
lightDensity = 45000.0,
nbkLevel = 30.5
nbkLevel = 30.5,
soilPh = 8.0
)

/*
Expand All @@ -150,18 +161,28 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {
val lightDensity = view?.findViewById<TextView>(R.id.textViewLightDensity)
val nbkLevel = view?.findViewById<TextView>(R.id.textViewNBKLevel)

temperature?.text = "${mockFarmConditions.temperature}&#176;C"
// As kotlin does not interpret html symbol entities
temperature?.text = "${mockFarmConditions.temperature}°C"
humidity?.text = "${mockFarmConditions.humidity}%"
soilMoisture?.text = "${mockFarmConditions.soilMoisture}%"
windspeed?.text = "${mockFarmConditions.windspeed} m/s"
precipitation?.text = "${mockFarmConditions.precipitation} mm"
lightDensity?.text = "${mockFarmConditions.windspeed} lux"
nbkLevel?.text = "${mockFarmConditions.nbkLevel} %"
view?.findViewById<TextView>(R.id.textViewSoilPH)?.text = "${mockFarmConditions.soilPh}"
}

override fun onMapReady(p0: GoogleMap) {
googleMap = p0

// handle no internet connection when map is not loaded
if (!isInternetAvailable(requireContext())) {
showInternetErrorDialog()
return
} else {
displaySnackBar("Farm Location update successful")
}

// Check Map Location Permission
if (ActivityCompat.checkSelfPermission(
requireContext(),
Expand Down Expand Up @@ -193,6 +214,10 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {
* Handle lifecycle for the Map
* This is implemented in order for it to work pretty well
*/
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
Expand All @@ -207,4 +232,20 @@ class MonitorFarmConditionFragment : Fragment(),OnMapReadyCallback {
super.onLowMemory()
mapView.onLowMemory()
}

private fun showInternetErrorDialog() {
AlertDialog.Builder(requireContext())
.setTitle("No Internet Connection to load map")
.setMessage("Please check your internet connection and try again.")
.setPositiveButton("Retry") { dialog, _ ->
dialog.dismiss()
// Retry the map load or any other action
mapView.getMapAsync(this@MonitorFarmConditionFragment)
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ package com.steve_md.smartmkulima.ui.fragments.main

import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.steve_md.smartmkulima.databinding.FragmentPredictYourCropProductionBinding
import com.steve_md.smartmkulima.utils.displaySnackBar
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber


@AndroidEntryPoint
class PredictYourCropProductionFragment : Fragment() {

private lateinit var binding:FragmentPredictYourCropProductionBinding
private lateinit var binding: FragmentPredictYourCropProductionBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand All @@ -32,54 +32,62 @@ class PredictYourCropProductionFragment : Fragment() {
findNavController().navigateUp()
}

binding.buttonGetGPSData.setOnClickListener {
binding.inputTemperature.setText("25.8")
binding.inputRainfall.setText("1200")
binding.inputHumidity.setText("67.7")
}

binding.buttonGetIotSensorData.setOnClickListener {
binding.inputSoilQualityPh.setText("5")
binding.inputNBKLevel.setText("30.5")
}

binding.buttonPredict.setOnClickListener {
val temperature = binding.inputTemperature.text.toString().toDouble().toString()
val rainfall = binding.inputRainfall.text.toString().toDouble().toString()
val soilQualityPh = binding.inputSoilQualityPh.text.toString()
val selectedCrop = binding.spinnerCropPredict.selectedItem.toString()
val selectedSeason = binding.spinnerSowingSeason.selectedItem.toString()
val humidity = binding.inputHumidity.text.toString().toDouble().toString()


if (temperature.isNotEmpty() && rainfall.isNotEmpty() && soilQualityPh.isNotEmpty()) {

if (temperature.isEmpty() && rainfall.isEmpty() && soilQualityPh.isEmpty()) {
displaySnackBar("Required fields!")
binding.enterTemperature.error = "Empty strings"
binding.enterRainfall.error = "Empty strings"
binding.enterSoilQualityPh.error = "Empty strings"
Timber.tag(this.tag.toString()).v("Em")
} else {

}
// call prediction function here :)
if (temperature != null && rainfall != null && selectedCrop!= null && selectedSeason!=null){
val prediction = predictFarmProduce(
temperature.toDouble(),
rainfall.toDouble(),
soilQualityPh,
selectedSeason,
selectedCrop
humidity.toDouble()
)
binding.textViewResult.text = "Expected production: $prediction"
Timber.tag(this.tag.toString()).v("Prediction: $prediction")
}
else {
binding.enterTemperature.error = "Empty strings"
binding.enterRainfall.error = "Empty strings"
binding.enterSoilQualityPh.error = "Empty strings"
Timber.tag(this.tag.toString()).v("Em")

binding.textViewResult.text = "Expected production: $prediction tonnes"
Timber.tag(this.tag.toString()).v("Prediction: $prediction tonnes")
}
}
}

// Dummy data for testing
private fun predictFarmProduce(
temperature: Double,
rainfall: Double,
soilQualityPh: String,
selectedSeason: String,
selectedCrop: String,
): Double {
humidity: Double,
): Double {
/**
* Linear Regression Algorithm
*/
val intercept = 10.0
val tempCoeff = 2.0
val rainCoeff = 1.5
val soilQualityPhValue = soilQualityPh.toDoubleOrNull() ?: 7.0 //Default soil ph to be 7.0 if not provided
val prediction = intercept + (temperature * tempCoeff) + (rainfall * rainCoeff) + (soilQualityPhValue*0.5)
val prediction = intercept + (humidity * tempCoeff) + (temperature * tempCoeff) + (rainfall * rainCoeff) + (soilQualityPhValue*0.5)

return prediction
}
Expand Down
Loading

0 comments on commit 3e8d183

Please sign in to comment.