Compare commits

...

2 Commits

9 changed files with 86 additions and 12 deletions

View File

@ -13,7 +13,8 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".ui.activities.StatisticsActivity"/> <activity android:name=".ui.activities.BoundaryEditorActivity"></activity>
<activity android:name=".ui.activities.StatisticsActivity" />
<activity android:name=".ui.activities.CollarDetailActivity" /> <activity android:name=".ui.activities.CollarDetailActivity" />
<activity android:name=".ui.activities.CollarListActivity" /> <activity android:name=".ui.activities.CollarListActivity" />
<activity android:name=".ui.activities.MainActivity"> <activity android:name=".ui.activities.MainActivity">

View File

@ -6,5 +6,6 @@ package com.danilafe.fencelessgrazing.model
* @param id the collar's internal unique identifier. * @param id the collar's internal unique identifier.
* @param name the collar's current designation in the system. * @param name the collar's current designation in the system.
* @param stimulus the number of stimulus activation reports in the last 24 hours. * @param stimulus the number of stimulus activation reports in the last 24 hours.
* @param boundary the collar's current valid grazing boundary.
*/ */
data class CollarDetails(val id: Int, val name: String, val stimulus: Int) data class CollarDetails(val id: Int, val name: String, val stimulus: Int, val boundary: List<CollarPos>)

View File

@ -2,6 +2,7 @@ package com.danilafe.fencelessgrazing.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import org.osmdroid.util.GeoPoint
/** /**
* GPS coordinate returned by many of the project's API endpoints. * GPS coordinate returned by many of the project's API endpoints.
@ -10,5 +11,7 @@ import kotlinx.android.parcel.Parcelize
* @param latitude the latitude of the GPS point. * @param latitude the latitude of the GPS point.
*/ */
@Parcelize @Parcelize
data class CollarPos(val longitude: String, val latitude: String) : Parcelable data class CollarPos(val longitude: String, val latitude: String) : Parcelable {
fun toGeoPoint(): GeoPoint = GeoPoint(latitude.toDouble(), longitude.toDouble())
}

View File

@ -34,9 +34,7 @@ class BoundaryEditorActivity : AppCompatActivity(),
setContentView(R.layout.activity_boundary_editor) setContentView(R.layout.activity_boundary_editor)
val requestedCenter = intent.getParcelableExtra<CollarPos>("center")!! val requestedCenter = intent.getParcelableExtra<CollarPos>("center")!!
center = GeoPoint( center = requestedCenter.toGeoPoint()
requestedCenter.longitude.toDouble(),
requestedCenter.latitude.toDouble())
identifier = intent.getIntExtra("identifier", -1) identifier = intent.getIntExtra("identifier", -1)
queue = Volley.newRequestQueue(applicationContext) queue = Volley.newRequestQueue(applicationContext)

View File

@ -1,15 +1,18 @@
package com.danilafe.fencelessgrazing.ui.activities package com.danilafe.fencelessgrazing.ui.activities
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import com.android.volley.RequestQueue import com.android.volley.RequestQueue
import com.android.volley.Response import com.android.volley.Response
import com.android.volley.toolbox.Volley import com.android.volley.toolbox.Volley
import com.danilafe.fencelessgrazing.R import com.danilafe.fencelessgrazing.R
import com.danilafe.fencelessgrazing.model.CollarPos
import com.danilafe.fencelessgrazing.model.Polygon import com.danilafe.fencelessgrazing.model.Polygon
import com.danilafe.fencelessgrazing.requests.authenticated.CollarDetailRequest import com.danilafe.fencelessgrazing.requests.authenticated.CollarDetailRequest
import com.danilafe.fencelessgrazing.requests.authenticated.CollarHistoryRequest import com.danilafe.fencelessgrazing.requests.authenticated.CollarHistoryRequest
@ -174,7 +177,7 @@ class CollarDetailActivity : AppCompatActivity() {
token, token,
Response.Listener { Response.Listener {
dataPoints = dataPoints =
it.map { p -> GeoPoint(p.longitude.toDouble(), p.latitude.toDouble()) } it.map { p -> p.toGeoPoint() }
}, },
Response.ErrorListener { Response.ErrorListener {
Toast.makeText(this, "Failed to retrieve history of collar", Toast.LENGTH_SHORT) Toast.makeText(this, "Failed to retrieve history of collar", Toast.LENGTH_SHORT)
@ -199,6 +202,16 @@ class CollarDetailActivity : AppCompatActivity() {
queue.add(detailRequest) queue.add(detailRequest)
} }
fun changeGrazingBoundary(v : View) {
startActivity(Intent(this, BoundaryEditorActivity::class.java).apply {
val center = dataPoints.lastOrNull()?.let {
CollarPos(it.longitude.toString(), it.latitude.toString())
} ?: CollarPos("0", "0")
putExtra("center", center)
putExtra("identifier", collarId)
})
}
/** /**
* Given a new location history of the animal, configures * Given a new location history of the animal, configures
* updates the [map] to include the relevant elements (such as the animal's * updates the [map] to include the relevant elements (such as the animal's
@ -235,7 +248,7 @@ class CollarDetailActivity : AppCompatActivity() {
*/ */
private fun updateBoundingBox(oldValue: Polygon?, polygon: Polygon) { private fun updateBoundingBox(oldValue: Polygon?, polygon: Polygon) {
if(oldValue == null) map.overlays.add(mapPolygon) if(oldValue == null) map.overlays.add(mapPolygon)
val points = polygon.dataPoints.map { GeoPoint(it.latitude.toDouble(), it.longitude.toDouble()) } val points = polygon.dataPoints.map { it.toGeoPoint() }
val polygonPoints = points.toMutableList() val polygonPoints = points.toMutableList()
polygonPoints.add(polygonPoints[0]) polygonPoints.add(polygonPoints[0])
mapPolygon.points = polygonPoints mapPolygon.points = polygonPoints

View File

@ -196,7 +196,7 @@ class CollarListActivity : AppCompatActivity() {
centerSet = true centerSet = true
val averageLongitude = summaries.map { it.pos.longitude.toDouble() }.average() val averageLongitude = summaries.map { it.pos.longitude.toDouble() }.average()
val averageLatitude = summaries.map { it.pos.latitude.toDouble() }.average() val averageLatitude = summaries.map { it.pos.latitude.toDouble() }.average()
map.controller.setCenter(GeoPoint(averageLongitude, averageLatitude)) map.controller.setCenter(GeoPoint(averageLatitude, averageLongitude))
} }
/** /**
@ -208,7 +208,7 @@ class CollarListActivity : AppCompatActivity() {
// Create or update overlay // Create or update overlay
val overlay = collarOverlays[it.id] ?: Marker(map) val overlay = collarOverlays[it.id] ?: Marker(map)
overlay.title = it.name overlay.title = it.name
overlay.position = GeoPoint(it.pos.longitude.toDouble(), it.pos.latitude.toDouble()) overlay.position = it.pos.toGeoPoint()
// Store new / existing overlay. // Store new / existing overlay.
if(!collarOverlays.containsKey(it.id)) map.overlays.add(overlay) if(!collarOverlays.containsKey(it.id)) map.overlays.add(overlay)

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activities.BoundaryEditorActivity">
<org.osmdroid.views.MapView
android:id="@+id/editorMap"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/saveButtonLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</org.osmdroid.views.MapView>
<LinearLayout
android:id="@+id/saveButtonLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:onClick="sendBoundary"
android:id="@+id/saveBoundaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/saveBoundary" />
<Button
android:onClick="addMarker"
android:id="@+id/addVertexButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/addVertex" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -29,9 +29,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/collarName" /> app:layout_constraintTop_toBottomOf="@+id/changeBoundaryButton" />
<TextView <TextView
android:id="@+id/collarStimulus" android:id="@+id/collarStimulus"
@ -42,4 +41,14 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/collarPos" /> app:layout_constraintTop_toBottomOf="@+id/collarPos" />
<Button
android:id="@+id/changeBoundaryButton"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="changeGrazingBoundary"
android:text="@string/changeGrazingBoundary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/collarName" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,4 +8,7 @@
<string name="collarSummaryStimulus">Required %1d stimuli in the last 24 hours</string> <string name="collarSummaryStimulus">Required %1d stimuli in the last 24 hours</string>
<string name="distanceTraveled">Distance Traveled</string> <string name="distanceTraveled">Distance Traveled</string>
<string name="viewStatistics">View Statistics</string> <string name="viewStatistics">View Statistics</string>
<string name="saveBoundary">Save Boundary</string>
<string name="addVertex">Add Vertex</string>
<string name="changeGrazingBoundary">Change Grazing Boundary</string>
</resources> </resources>