Working on cache downloader

pull/489/head
PWRxPSYCHO 2022-09-22 08:35:33 -04:00
rodzic 4d81689f21
commit 9b1dfb0d02
5 zmienionych plików z 424 dodań i 16 usunięć

Wyświetl plik

@ -140,9 +140,11 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version" kapt "com.google.dagger:hilt-compiler:$hilt_version"
//OSMAND //OSMDROID, mgrs,
implementation 'org.osmdroid:osmdroid-android:6.1.14' implementation 'org.osmdroid:osmdroid-android:6.1.14'
implementation 'com.github.MKergall:osmbonuspack:6.9.0'
implementation 'mil.nga:mgrs:2.0.0' implementation 'mil.nga:mgrs:2.0.0'
implementation 'org.osmdroid:osmdroid-geopackage:6.1.14'
// optional - Kotlin Extensions and Coroutines support for Room // optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"

Wyświetl plik

@ -37,6 +37,9 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.osmdroid.bonuspack.kml.KmlDocument
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.FolderOverlay
import java.io.BufferedWriter import java.io.BufferedWriter
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.FileWriter import java.io.FileWriter
@ -480,6 +483,31 @@ class UIViewModel @Inject constructor(
} }
} }
fun parseUrl(url: String, map: MapView) {
viewModelScope.launch(Dispatchers.IO) {
parseIt(url, map)
}
}
// For Future Use
// model.parseUrl(
// "https://www.google.com/maps/d/kml?forcekml=1&mid=1FmqWhZG3PG3dY92x9yf2RlREcK7kMZs&lid=-ivSjBCePsM",
// map
// )
private fun parseIt(url: String, map: MapView) {
val kmlDoc = KmlDocument()
try {
kmlDoc.parseKMLUrl(url)
val kmlOverlay = kmlDoc.mKmlRoot.buildOverlay(map, null, null, kmlDoc) as FolderOverlay
kmlDoc.mKmlRoot.mItems
kmlDoc.mKmlRoot.mName
map.overlayManager.overlays().add(kmlOverlay)
} catch (ex: Exception) {
debug("Failed to download .kml $ex")
}
}
fun addQuickChatAction(name: String, value: String, mode: QuickChatAction.Mode) { fun addQuickChatAction(name: String, value: String, mode: QuickChatAction.Mode) {
viewModelScope.launch(Dispatchers.Main) { viewModelScope.launch(Dispatchers.Main) {
val action = QuickChatAction(0, name, value, mode, _quickChatActions.value.size) val action = QuickChatAction(0, name, value, mode, _quickChatActions.value.size)

Wyświetl plik

@ -1,5 +1,6 @@
package com.geeksville.mesh.ui package com.geeksville.mesh.ui
import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Canvas import android.graphics.Canvas
@ -7,33 +8,41 @@ import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.*
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.BuildConfig import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.databinding.MapViewBinding import com.geeksville.mesh.databinding.MapViewBinding
import com.geeksville.mesh.model.CustomTileSource import com.geeksville.mesh.model.CustomTileSource
import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.util.formatAgo import com.geeksville.mesh.util.formatAgo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.api.IMapController import org.osmdroid.api.IMapController
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.cachemanager.CacheManager
import org.osmdroid.tileprovider.tilesource.ITileSource import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.util.BoundingBox import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay import org.osmdroid.views.overlay.*
import org.osmdroid.views.overlay.Marker import kotlin.math.pow
@AndroidEntryPoint @AndroidEntryPoint
class MapFragment : ScreenFragment("Map"), Logging { class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener, OnSeekBarChangeListener,
TextWatcher {
private lateinit var binding: MapViewBinding private lateinit var binding: MapViewBinding
private lateinit var map: MapView private lateinit var map: MapView
@ -41,20 +50,38 @@ class MapFragment : ScreenFragment("Map"), Logging {
private lateinit var mPrefs: SharedPreferences private lateinit var mPrefs: SharedPreferences
private val model: UIViewModel by activityViewModels() private val model: UIViewModel by activityViewModels()
private lateinit var cacheManager: CacheManager
private lateinit var btnCache: FloatingActionButton
private lateinit var cacheNorth: EditText
private lateinit var cacheSouth: EditText
private lateinit var cacheEast: EditText
private lateinit var cacheWest: EditText
private lateinit var cacheEstimate: TextView
private lateinit var zoomMin: SeekBar
private lateinit var zoomMax: SeekBar
private var downloadPrompt: AlertDialog? = null
private var alertDialog: AlertDialog? = null
private lateinit var executeJob: Button
private val defaultMinZoom = 1.5 private val defaultMinZoom = 1.5
private val nodeZoomLevel = 8.5 private val nodeZoomLevel = 8.5
private val defaultZoomSpeed = 3000L private val defaultZoomSpeed = 3000L
private val prefsName = "org.andnav.osm.prefs" private val prefsName = "org.geeksville.osm.prefs"
private val mapStyleId = "map_style_id" private val mapStyleId = "map_style_id"
private var nodePositions = listOf<MarkerWithLabel>() private var nodePositions = listOf<MarkerWithLabel>()
private val nodeLayer = 1 private val nodeLayer = 1
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
savedInstanceState: Bundle?
): View { ): View {
binding = MapViewBinding.inflate(inflater) binding = MapViewBinding.inflate(inflater)
btnCache = binding.root.findViewById(R.id.downloadButton)
return binding.root return binding.root
} }
@ -89,6 +116,191 @@ class MapFragment : ScreenFragment("Map"), Logging {
} }
zoomToNodes(mapController) zoomToNodes(mapController)
} }
btnCache.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
R.id.executeJob -> updateEstimate(true)
R.id.downloadButton -> showCacheManagerDialog()
}
}
private fun showCacheManagerDialog() {
val alertDialogBuilder = AlertDialog.Builder(
activity
)
// set title
alertDialogBuilder.setTitle("Cache Manager")
//.setMessage(R.string.cache_manager_description);
// set dialog message
alertDialogBuilder.setItems(
arrayOf<CharSequence>(
"Cache current size",
"Cache Download",
resources.getString(R.string.cancel)
)
) { dialog, which ->
when (which) {
0 -> showCurrentCacheInfo()
1 -> {
downloadJobAlert()
dialog.dismiss()
}
else -> dialog.dismiss()
}
}
// create alert dialog
alertDialog = alertDialogBuilder.create()
// show it
alertDialog!!.show()
}
private fun showCurrentCacheInfo() {
Toast.makeText(activity, "Calculating...", Toast.LENGTH_SHORT).show()
Thread {
val alertDialogBuilder = AlertDialog.Builder(
activity
)
// set title
alertDialogBuilder.setTitle("Cache Manager")
.setMessage(
"""
Cache Capacity (mb): ${cacheManager.cacheCapacity() * 2.0.pow(-20.0)}
Cache Usage (mb): ${cacheManager.currentCacheUsage() * 2.0.pow(-20.0)}
""".trimIndent()
)
// set dialog message
alertDialogBuilder.setItems(
arrayOf<CharSequence>(
resources.getString(R.string.cancel)
)
) { dialog, _ -> dialog.dismiss() }
activity!!.runOnUiThread { // show it
// create alert dialog
val alertDialog = alertDialogBuilder.create()
alertDialog.show()
}
}.start()
}
private fun downloadJobAlert() {
//prompt for input params
val builder = AlertDialog.Builder(activity)
val view = View.inflate(activity, R.layout.cache_mgr_input, null)
val boundingBox: BoundingBox = map.boundingBox
zoomMax = view.findViewById(R.id.slider_zoom_max)
zoomMax.max = map.maxZoomLevel.toInt()
zoomMax.setOnSeekBarChangeListener(this)
zoomMin = view.findViewById(R.id.slider_zoom_min)
zoomMin.max = map.maxZoomLevel.toInt()
zoomMin.progress = map.minZoomLevel.toInt()
zoomMin.setOnSeekBarChangeListener(this)
cacheEast = view.findViewById(R.id.cache_east)
cacheEast.setText(boundingBox.lonEast.toString() + "")
cacheNorth = view.findViewById(R.id.cache_north)
cacheNorth.setText(boundingBox.latNorth.toString() + "")
cacheSouth = view.findViewById(R.id.cache_south)
cacheSouth.setText(boundingBox.latSouth.toString() + "")
cacheWest = view.findViewById(R.id.cache_west)
cacheWest.setText(boundingBox.lonWest.toString() + "")
cacheEstimate = view.findViewById(R.id.cache_estimate)
//change listeners for both validation and to trigger the download estimation
cacheEast.addTextChangedListener(this)
cacheNorth.addTextChangedListener(this)
cacheSouth.addTextChangedListener(this)
cacheWest.addTextChangedListener(this)
executeJob = view.findViewById(R.id.executeJob)
executeJob.setOnClickListener {
builder.setOnCancelListener {
cacheEast.text = null
cacheSouth.text = null
cacheEstimate.text = ""
cacheNorth.text = null
cacheWest.text = null
zoomMin.progress = 0
zoomMax.progress = 0
}
}
builder.setView(view)
builder.setCancelable(true)
downloadPrompt = builder.create()
downloadPrompt!!.show()
}
/**
* if true, start the job
* if false, just update the dialog box
*/
private fun updateEstimate(startJob: Boolean) {
try {
if (cacheWest.text != null && cacheNorth.text != null && cacheSouth.text != null && zoomMax.progress != 0 && zoomMin.progress != 0) {
val n: Double = cacheNorth.text.toString().toDouble()
val s: Double = cacheSouth.text.toString().toDouble()
val e: Double = cacheEast.text.toString().toDouble()
val w: Double = cacheWest.text.toString().toDouble()
val zoommin: Int = zoomMin.progress
val zoommax: Int = zoomMax.progress
//nesw
val bb = BoundingBox(n, e, s, w)
val tilecount: Int = cacheManager.possibleTilesInArea(bb, zoommin, zoommax)
cacheEstimate.text = ("$tilecount tiles")
if (startJob) {
if (downloadPrompt != null) {
downloadPrompt!!.dismiss()
downloadPrompt = null
}
//this triggers the download
cacheManager.downloadAreaAsync(activity,
bb,
zoommin,
zoommax,
object : CacheManager.CacheManagerCallback {
override fun onTaskComplete() {
Toast.makeText(activity, "Download complete!", Toast.LENGTH_LONG)
.show()
}
override fun onTaskFailed(errors: Int) {
Toast.makeText(
activity,
"Download complete with $errors errors",
Toast.LENGTH_LONG
).show()
}
override fun updateProgress(
progress: Int, currentZoomLevel: Int, zoomMin: Int, zoomMax: Int
) {
//NOOP since we are using the build in UI
}
override fun downloadStarted() {
//NOOP since we are using the build in UI
}
override fun setPossibleTilesInArea(total: Int) {
//NOOP since we are using the build in UI
}
})
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
} }
private fun chooseMapStyle() { private fun chooseMapStyle() {
@ -138,8 +350,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
marker.position = GeoPoint(p.latitude, p.longitude) marker.position = GeoPoint(p.latitude, p.longitude)
marker.icon = ContextCompat.getDrawable( marker.icon = ContextCompat.getDrawable(
requireActivity(), requireActivity(), R.drawable.ic_baseline_location_on_24
R.drawable.ic_baseline_location_on_24
) )
} }
marker marker
@ -159,8 +370,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
* Adds copyright to map depending on what source is showing * Adds copyright to map depending on what source is showing
*/ */
private fun addCopyright() { private fun addCopyright() {
val copyrightNotice: String = val copyrightNotice: String = map.tileProvider.tileSource.copyrightNotice
map.tileProvider.tileSource.copyrightNotice
val copyrightOverlay = CopyrightOverlay(context) val copyrightOverlay = CopyrightOverlay(context)
copyrightOverlay.setCopyrightNotice(copyrightNotice) copyrightOverlay.setCopyrightNotice(copyrightNotice)
map.overlays.add(copyrightOverlay) map.overlays.add(copyrightOverlay)
@ -168,6 +378,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
private fun setupMapProperties() { private fun setupMapProperties() {
if (this::map.isInitialized) { if (this::map.isInitialized) {
cacheManager = CacheManager(map)
map.setDestroyMode(false) // keeps map instance alive when in the background. map.setDestroyMode(false) // keeps map instance alive when in the background.
map.isVerticalMapRepetitionEnabled = false // disables map repetition map.isVerticalMapRepetitionEnabled = false // disables map repetition
map.setScrollableAreaLimitLatitude( map.setScrollableAreaLimitLatitude(
@ -194,8 +405,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
nodesWithPosition.forEach { nodesWithPosition.forEach {
points.add( points.add(
GeoPoint( GeoPoint(
it.position!!.latitude, it.position!!.latitude, it.position!!.longitude
it.position!!.longitude
) )
) )
} }
@ -219,6 +429,12 @@ class MapFragment : ScreenFragment("Map"), Logging {
override fun onPause() { override fun onPause() {
map.onPause() map.onPause()
if (alertDialog != null && alertDialog!!.isShowing) {
alertDialog!!.dismiss()
}
if (downloadPrompt != null && downloadPrompt!!.isShowing) {
downloadPrompt!!.dismiss()
}
super.onPause() super.onPause()
} }
@ -270,6 +486,26 @@ class MapFragment : ScreenFragment("Map"), Logging {
c.drawText(mLabel, (p.x - 0f), (p.y - 110f), textPaint) c.drawText(mLabel, (p.x - 0f), (p.y - 110f), textPaint)
} }
} }
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
updateEstimate(false)
}
override fun onStartTrackingTouch(p0: SeekBar?) {
}
override fun onStopTrackingTouch(p0: SeekBar?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
updateEstimate(false)
}
override fun afterTextChanged(p0: Editable?) {
}
} }

Wyświetl plik

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Northern most Latitude"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/cache_north"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:digits="1234567890.-"
android:inputType="number|numberDecimal|numberSigned" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Southern most Latitude"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/cache_south"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:digits="1234567890.-"
android:inputType="number|numberDecimal|numberSigned" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Eastern most Longitude"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/cache_east"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:digits="1234567890.-"
android:inputType="number|numberDecimal|numberSigned" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Western most Longitude"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/cache_west"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:digits="1234567890.-"
android:inputType="number|numberDecimal|numberSigned" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Zoom Level Min"
android:textAppearance="?android:attr/textAppearanceLarge" />
<SeekBar
android:id="@+id/slider_zoom_min"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Zoom Level Max"
android:textAppearance="?android:attr/textAppearanceLarge" />
<SeekBar
android:id="@+id/slider_zoom_max"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tile download estimate:"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/cache_estimate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<LinearLayout
android:id="@+id/cache_archival_section"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Archive File name:"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/cache_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="archive.sqlite"
android:lines="1"
android:text="archive.sqlite"
/>
</LinearLayout>
<Button
android:id="@+id/executeJob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Download" />
</LinearLayout>
</ScrollView>
</LinearLayout>

Wyświetl plik

@ -25,15 +25,15 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_style_toggle" android:id="@+id/downloadButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:backgroundTint="@color/design_default_color_secondary" android:backgroundTint="@color/design_default_color_secondary"
android:contentDescription="@string/style_selection" android:contentDescription="@string/style_selection"
android:orientation="vertical" android:orientation="vertical"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
tools:background="@color/design_default_color_secondary" /> tools:background="@color/design_default_color_secondary" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>