Merge pull request #489 from ScriptTactics/osmdroid-phase2

Osmdroid Phase 2
master
Andre K 2022-10-07 22:40:05 -03:00 zatwierdzone przez GitHub
commit e3b12fc74c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
10 zmienionych plików z 839 dodań i 95 usunięć

Wyświetl plik

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

Wyświetl plik

@ -1,55 +0,0 @@
package com.geeksville.mesh.model
import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.tileprovider.tilesource.TileSourcePolicy
import org.osmdroid.util.MapTileIndex
class CustomTileSource {
companion object {
val ESRI_IMAGERY = object : OnlineTileSourceBase(
"ESRI World Overview", 0, 18, 256, "", arrayOf(
"https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/WMTS/1.0.0/default028mm/MapServer/tile/"
), "Esri, Maxar, Earthstar Geographics, and the GIS User Community" +
"URL\n" +
"View\n",
TileSourcePolicy(
2, TileSourcePolicy.FLAG_NO_BULK
or TileSourcePolicy.FLAG_NO_PREVENTIVE
or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL
or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED
)
) {
override fun getTileURLString(pMapTileIndex: Long): String {
return baseUrl + (MapTileIndex.getZoom(pMapTileIndex)
.toString() + "/" + MapTileIndex.getY(pMapTileIndex)
+ "/" + MapTileIndex.getX(pMapTileIndex)
+ mImageFilenameEnding)
}
}
val MAPNIK: OnlineTileSourceBase = TileSourceFactory.MAPNIK
val USGS_TOPO: OnlineTileSourceBase = TileSourceFactory.USGS_TOPO
val USGS_SAT: OnlineTileSourceBase = TileSourceFactory.USGS_SAT
val DEFAULT_TILE_SOURCE: OnlineTileSourceBase = TileSourceFactory.DEFAULT_TILE_SOURCE
/**
* The order in this list must match that in the arrays.xml under map_styles
*/
val mTileSources: List<ITileSource> =
listOf(MAPNIK, USGS_TOPO, USGS_SAT, ESRI_IMAGERY)
fun getTileSource(aName: String): ITileSource {
for (tileSource: ITileSource in mTileSources) {
if (tileSource.name().equals(aName)) {
return tileSource;
}
}
throw IllegalArgumentException("No such tile source: $aName")
}
}
}

Wyświetl plik

@ -37,6 +37,9 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
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.FileNotFoundException
import java.io.FileWriter
@ -495,6 +498,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) {
viewModelScope.launch(Dispatchers.Main) {
val action = QuickChatAction(0, name, value, mode, _quickChatActions.value.size)

Wyświetl plik

@ -0,0 +1,44 @@
package com.geeksville.mesh.model.map
import android.content.Context
import android.view.MotionEvent
import org.osmdroid.tileprovider.MapTileProviderBase
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.DefaultOverlayManager
import org.osmdroid.views.overlay.TilesOverlay
class CustomOverlayManager
/**
* Default constructor
*/
(tilesOverlay: TilesOverlay?) : DefaultOverlayManager(tilesOverlay) {
/**
* Override event & do nothing
*/
override fun onDoubleTap(e: MotionEvent?, pMapView: MapView?): Boolean {
return true
}
/**
* Override event & do nothing
*/
override fun onDoubleTapEvent(e: MotionEvent?, pMapView: MapView?): Boolean {
return true
}
companion object {
/**
* Create MyOverlayManager
*/
fun create(mapView: MapView, context: Context?): CustomOverlayManager {
val mTileProvider: MapTileProviderBase = mapView.tileProvider
val tilesOverlay = TilesOverlay(mTileProvider, context)
mapView.tileProvider
mapView.overlayManager = CustomOverlayManager(tilesOverlay)
//mapView.overlayManager.overlays().add(overlay)
mapView.invalidate()
return CustomOverlayManager(tilesOverlay)
}
}
}

Wyświetl plik

@ -0,0 +1,171 @@
package com.geeksville.mesh.model.map
import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.tileprovider.tilesource.TileSourcePolicy
import org.osmdroid.util.MapTileIndex
class CustomTileSource {
companion object {
// Map Server information: https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer
// Arcgis Information: https://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9
private val ESRI_IMAGERY = object : OnlineTileSourceBase(
"ESRI World Overview", 0, 18, 256, ".jpg", arrayOf(
"https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/"
), "Esri, Maxar, Earthstar Geographics, and the GIS User Community",
TileSourcePolicy(
4,
TileSourcePolicy.FLAG_NO_BULK
or TileSourcePolicy.FLAG_NO_PREVENTIVE
or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL
or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED
)
) {
override fun getTileURLString(pMapTileIndex: Long): String {
return baseUrl + (MapTileIndex.getZoom(pMapTileIndex)
.toString() + "/" + MapTileIndex.getY(pMapTileIndex)
+ "/" + MapTileIndex.getX(pMapTileIndex)
+ mImageFilenameEnding)
}
}
//Transparent Background
//https://earthlive.maptiles.arcgis.com/arcgis/rest/services/GOES/GOES31D/MapServer/tile/
private val NOAA_RADAR = object : OnlineTileSourceBase(
"NOAA GOES Radar",
0,
18,
256,
"",
arrayOf(
"https://earthlive.maptiles.arcgis.com/arcgis/rest/services/GOES/GOES31C/MapServer/tile/"
),
"Dataset Citation: GOES-R Calibration Working Group and GOES-R Series Program, (2017): NOAA GOES-R Series Advanced Baseline Imager (ABI) Level 1b Radiances Band 13. NOAA National Centers for Environmental Information. doi:10.7289/V5BV7DSR",
TileSourcePolicy(
2,
TileSourcePolicy.FLAG_NO_BULK
or TileSourcePolicy.FLAG_NO_PREVENTIVE
or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL
or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED
)
) {
override fun getTileURLString(pMapTileIndex: Long): String {
return baseUrl + (MapTileIndex.getZoom(pMapTileIndex)
.toString() + "/" + MapTileIndex.getY(pMapTileIndex)
+ "/" + MapTileIndex.getX(pMapTileIndex)
+ mImageFilenameEnding)
}
}
private val USGS_HYDRO_CACHE = object : OnlineTileSourceBase(
"USGS Hydro Cache",
0,
18,
256,
"",
arrayOf(
"https://basemap.nationalmap.gov/arcgis/rest/services/USGSHydroCached/MapServer/tile/"
),
"USGS",
TileSourcePolicy(
2,
TileSourcePolicy.FLAG_NO_PREVENTIVE
or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL
or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED
)
) {
override fun getTileURLString(pMapTileIndex: Long): String {
return baseUrl + (MapTileIndex.getZoom(pMapTileIndex)
.toString() + "/" + MapTileIndex.getY(pMapTileIndex)
+ "/" + MapTileIndex.getX(pMapTileIndex)
+ mImageFilenameEnding)
}
}
private val USGS_SHADED_RELIEF = object : OnlineTileSourceBase(
"USGS Shaded Relief Only",
0,
18,
256,
"",
arrayOf(
"https://basemap.nationalmap.gov/arcgis/rest/services/USGSShadedReliefOnly/MapServer/tile/"
),
"USGS",
TileSourcePolicy(
2,
TileSourcePolicy.FLAG_NO_PREVENTIVE
or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL
or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED
)
) {
override fun getTileURLString(pMapTileIndex: Long): String {
return baseUrl + (MapTileIndex.getZoom(pMapTileIndex)
.toString() + "/" + MapTileIndex.getY(pMapTileIndex)
+ "/" + MapTileIndex.getX(pMapTileIndex)
+ mImageFilenameEnding)
}
}
/**
* WMS TILE SERVER
* More research is required to get this to function correctly with overlays
*/
val NOAA_RADAR_WMS = NOAAWmsTileSource(
"Recent Weather Radar",
arrayOf("https://new.nowcoast.noaa.gov/arcgis/services/nowcoast/radar_meteo_imagery_nexrad_time/MapServer/WmsServer?"),
"1",
"1.3.0",
"",
"EPSG%3A3857",
"",
"image/png"
)
val NOAA_SATELLITE_RADAR_WMS = NOAAWmsTileSource(
"Weather Satellite Imagery",
arrayOf("https://new.nowcoast.noaa.gov/arcgis/services/nowcoast/sat_meteo_imagery_time/MapServer/WmsServer?"),
"1,5,9,13,17,21,25",
"1.3.0",
"",
"EPSG%3A3857",
"",
"image/png"
)
/**
* ===============================================================================================
*/
private val MAPNIK: OnlineTileSourceBase = TileSourceFactory.MAPNIK
private val USGS_TOPO: OnlineTileSourceBase = TileSourceFactory.USGS_TOPO
private val OPEN_TOPO: OnlineTileSourceBase = TileSourceFactory.OpenTopo
private val USGS_SAT: OnlineTileSourceBase = TileSourceFactory.USGS_SAT
private val SEAMAP: OnlineTileSourceBase = TileSourceFactory.OPEN_SEAMAP
val DEFAULT_TILE_SOURCE: OnlineTileSourceBase = TileSourceFactory.DEFAULT_TILE_SOURCE
/**
* The order in this list must match that in the arrays.xml under map_styles
*/
val mTileSources: List<ITileSource> =
listOf(
MAPNIK,
USGS_TOPO,
OPEN_TOPO,
USGS_SAT,
ESRI_IMAGERY,
)
fun getTileSource(aName: String): ITileSource {
for (tileSource: ITileSource in mTileSources) {
if (tileSource.name().equals(aName)) {
return tileSource;
}
}
throw IllegalArgumentException("No such tile source: $aName")
}
}
}

Wyświetl plik

@ -0,0 +1,146 @@
package com.geeksville.mesh.model.map
import android.content.res.Resources
import android.util.Log
import org.osmdroid.api.IMapView
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
import org.osmdroid.util.MapTileIndex
import kotlin.math.atan
import kotlin.math.pow
import kotlin.math.sinh
open class NOAAWmsTileSource(
aName: String,
aBaseUrl: Array<String>,
layername: String,
version: String,
time: String?,
crs: String,
style: String?,
format: String
) : OnlineTileSourceBase(aName, 0, 22, 256, "png", aBaseUrl) {
// array indexes for array to hold bounding boxes.
private val MINX = 0
private val MAXX = 1
private val MINY = 2
private val MAXY = 3
// Web Mercator n/w corner of the map.
private val TILE_ORIGIN = doubleArrayOf(-20037508.34789244, 20037508.34789244)
//array indexes for that data
private val ORIG_X = 0
private val ORIG_Y = 1 // "
// Size of square world map in meters, using WebMerc projection.
private val MAP_SIZE = 20037508.34789244 * 2
private var layer = ""
private var version = "1.1.0"
private var crs = "EPSG:3A3857" //used by geo server
private var size = ""
private var format = ""
private var time = ""
private var style: String? = null
private var forceHttps = false
private var forceHttp = false
init {
Log.i(IMapView.LOGTAG, "WMS support is BETA. Please report any issues")
layer = layername
this.version = version
this.crs = crs
this.style = style
this.format = format
if (time != null) this.time = time
}
// fun createFrom(endpoint: WMSEndpoint, layer: WMSLayer): WMSTileSource? {
// var srs: String? = "EPSG:900913"
// if (layer.srs.isNotEmpty()) {
// srs = layer.srs[0]
// }
// return if (layer.styles.isEmpty()) {
// WMSTileSource(
// layer.name, arrayOf(endpoint.baseurl), layer.name,
// endpoint.wmsVersion, srs, null, layer.pixelSize
// )
// } else WMSTileSource(
// layer.name, arrayOf(endpoint.baseurl), layer.name,
// endpoint.wmsVersion, srs, layer.styles[0], layer.pixelSize
// )
// }
private fun tile2lon(x: Int, z: Int): Double {
return x / 2.0.pow(z.toDouble()) * 360.0 - 180
}
private fun tile2lat(y: Int, z: Int): Double {
val n = Math.PI - 2.0 * Math.PI * y / 2.0.pow(z.toDouble())
return Math.toDegrees(atan(sinh(n)))
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
private fun getBoundingBox(x: Int, y: Int, zoom: Int): DoubleArray {
val tileSize = MAP_SIZE / 2.0.pow(zoom.toDouble())
val minx = TILE_ORIGIN[ORIG_X] + x * tileSize
val maxx = TILE_ORIGIN[ORIG_X] + (x + 1) * tileSize
val miny = TILE_ORIGIN[ORIG_Y] - (y + 1) * tileSize
val maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize
val bbox = DoubleArray(4)
bbox[MINX] = minx
bbox[MINY] = miny
bbox[MAXX] = maxx
bbox[MAXY] = maxy
return bbox
}
fun isForceHttps(): Boolean {
return forceHttps
}
fun setForceHttps(forceHttps: Boolean) {
this.forceHttps = forceHttps
}
fun isForceHttp(): Boolean {
return forceHttp
}
fun setForceHttp(forceHttp: Boolean) {
this.forceHttp = forceHttp
}
override fun getTileURLString(pMapTileIndex: Long): String? {
var baseUrl = baseUrl
if (forceHttps) baseUrl = baseUrl.replace("http://", "https://")
if (forceHttp) baseUrl = baseUrl.replace("https://", "http://")
val sb = StringBuilder(baseUrl)
if (!baseUrl.endsWith("&"))
sb.append("service=WMS")
sb.append("&request=GetMap")
sb.append("&version=").append(version)
sb.append("&layers=").append(layer)
if (style != null) sb.append("&styles=").append(style)
sb.append("&format=").append(format)
sb.append("&transparent=true")
sb.append("&height=").append(Resources.getSystem().displayMetrics.heightPixels)
sb.append("&width=").append(Resources.getSystem().displayMetrics.widthPixels)
sb.append("&crs=").append(crs)
sb.append("&bbox=")
val bbox = getBoundingBox(
MapTileIndex.getX(pMapTileIndex),
MapTileIndex.getY(pMapTileIndex),
MapTileIndex.getZoom(pMapTileIndex)
)
sb.append(bbox[MINX]).append(",")
sb.append(bbox[MINY]).append(",")
sb.append(bbox[MAXX]).append(",")
sb.append(bbox[MAXY])
Log.i(IMapView.LOGTAG, sb.toString())
return sb.toString()
}
}

Wyświetl plik

@ -1,5 +1,6 @@
package com.geeksville.mesh.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Canvas
@ -7,9 +8,9 @@ import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.Log
import android.view.*
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels
import com.geeksville.mesh.BuildConfig
@ -18,45 +19,81 @@ import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.databinding.MapViewBinding
import com.geeksville.mesh.model.CustomTileSource
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.map.CustomOverlayManager
import com.geeksville.mesh.model.map.CustomTileSource
import com.geeksville.mesh.util.formatAgo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.api.IMapController
import org.osmdroid.config.Configuration
import org.osmdroid.events.MapListener
import org.osmdroid.events.ScrollEvent
import org.osmdroid.events.ZoomEvent
import org.osmdroid.tileprovider.cachemanager.CacheManager
import org.osmdroid.tileprovider.cachemanager.CacheManager.CacheManagerCallback
import org.osmdroid.tileprovider.modules.SqlTileWriter
import org.osmdroid.tileprovider.modules.SqliteArchiveTileWriter
import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
import org.osmdroid.tileprovider.tilesource.TileSourcePolicyException
import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.*
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
import java.io.File
import kotlin.math.pow
@AndroidEntryPoint
class MapFragment : ScreenFragment("Map"), Logging {
class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener {
// UI Elements
private lateinit var binding: MapViewBinding
private lateinit var map: MapView
private lateinit var mapController: IMapController
private lateinit var mPrefs: SharedPreferences
private val model: UIViewModel by activityViewModels()
private lateinit var downloadBtn: FloatingActionButton
private lateinit var cacheEstimate: TextView
private lateinit var executeJob: Button
private var downloadPrompt: AlertDialog? = null
private var alertDialog: AlertDialog? = null
// constants
private val defaultMinZoom = 1.5
private val defaultMaxZoom = 18.0
private val nodeZoomLevel = 8.5
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 var nodePositions = listOf<MarkerWithLabel>()
private var wayPoints = listOf<MarkerWithLabel>()
private val nodeLayer = 1
// Distance of bottom corner to top corner of bounding box
private val zoomLevelLowest = 13.0 // approx 5 miles long
private val zoomLevelMiddle = 12.25 // approx 10 miles long
private val zoomLevelHighest = 11.5 // approx 15 miles long
private var zoomLevelMin = 0.0
private var zoomLevelMax = 0.0
// Map Elements
private lateinit var mapController: IMapController
private lateinit var mPrefs: SharedPreferences
private lateinit var writer: SqliteArchiveTileWriter
private val model: UIViewModel by activityViewModels()
private lateinit var cacheManager: CacheManager
private lateinit var downloadRegionBoundingBox: BoundingBox
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = MapViewBinding.inflate(inflater)
downloadBtn = binding.root.findViewById(R.id.downloadButton)
binding.cacheLayout.visibility = View.GONE
return binding.root
}
@ -69,29 +106,257 @@ class MapFragment : ScreenFragment("Map"), Logging {
setupMapProperties()
map.setTileSource(loadOnlineTileSourceBase())
renderDownloadButton()
map.let {
if (view != null) {
mapController = map.controller
binding.mapStyleButton.setOnClickListener {
chooseMapStyle()
}
model.nodeDB.nodes.value?.let { nodes ->
if (binding.cacheLayout.visibility == View.GONE) {
model.nodeDB.nodes.value?.let { nodes ->
onNodesChanged(nodes.values)
drawOverlays()
}
}
}
if (binding.cacheLayout.visibility == View.GONE) {
// Any times nodes change update our map
model.nodeDB.nodes.observe(viewLifecycleOwner) { nodes ->
onNodesChanged(nodes.values)
drawOverlays()
}
}
// Any times nodes change update our map
model.nodeDB.nodes.observe(viewLifecycleOwner) { nodes ->
onNodesChanged(nodes.values)
drawOverlays()
}
model.waypoints.observe(viewLifecycleOwner) {
debug("New waypoints received: ${it.size}")
onWaypointChanged(it.values)
drawOverlays()
model.waypoints.observe(viewLifecycleOwner) {
debug("New waypoints received: ${it.size}")
onWaypointChanged(it.values)
drawOverlays()
}
}
zoomToNodes(mapController)
}
downloadBtn.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
R.id.executeJob -> updateEstimate()
R.id.downloadButton -> showCacheManagerDialog()
R.id.box5miles -> generateBoxOverlay(zoomLevelLowest)
R.id.box10miles -> generateBoxOverlay(zoomLevelMiddle)
R.id.box15miles -> generateBoxOverlay(zoomLevelHighest)
}
}
private fun showCacheManagerDialog() {
val alertDialogBuilder = AlertDialog.Builder(
activity
)
// set title
alertDialogBuilder.setTitle("Offline Manager")
// set dialog message
alertDialogBuilder.setItems(
arrayOf<CharSequence>(
"Current Cache size",
"Download Region",
"Clear Downloaded Tiles",
resources.getString(R.string.cancel)
)
) { dialog, which ->
when (which) {
0 -> showCurrentCacheInfo()
1 -> {
downloadJobAlert()
dialog.dismiss()
}
2 -> clearCache()
else -> dialog.dismiss()
}
}
// create alert dialog
alertDialog = alertDialogBuilder.create()
// show it
alertDialog!!.show()
}
/**
* Clears active tile source cache
*/
private fun clearCache() {
val b: Boolean = SqlTileWriter().purgeCache()
SqlTileWriter().onDetach()
val title = if (b) "SQL Cache purged" else "SQL Cache purge failed, see logcat for details"
val length = if (b) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
Toast.makeText(activity, title, length).show()
alertDialog!!.dismiss()
}
private fun showCurrentCacheInfo() {
Toast.makeText(activity, "Calculating...", Toast.LENGTH_SHORT).show()
cacheManager = CacheManager(map) // Make sure CacheManager has latest from map
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 .
binding.downloadButton.hide()
binding.mapStyleButton.visibility = View.GONE
binding.cacheLayout.visibility = View.VISIBLE
val builder = AlertDialog.Builder(activity)
binding.box5miles.setOnClickListener(this)
binding.box10miles.setOnClickListener(this)
binding.box15miles.setOnClickListener(this)
cacheEstimate = binding.cacheEstimate
generateBoxOverlay(zoomLevelLowest)
executeJob = binding.executeJob
executeJob.setOnClickListener(this)
binding.cancelDownload.setOnClickListener {
cacheEstimate.text = ""
defaultMapSettings()
}
builder.setCancelable(true)
}
/**
* Reset map to default settings & visible buttons
*/
private fun defaultMapSettings() {
binding.downloadButton.show()
binding.mapStyleButton.visibility = View.VISIBLE
binding.cacheLayout.visibility = View.GONE
setupMapProperties()
drawOverlays()
}
/**
* Creates Box overlay showing what area can be downloaded
*/
private fun generateBoxOverlay(zoomLevel: Double) {
drawOverlays()
map.setMultiTouchControls(false)
zoomLevelMax = zoomLevel // furthest back
zoomLevelMin =
map.tileProvider.tileSource.maximumZoomLevel.toDouble() // furthest in min should be > than max
mapController.setZoom(zoomLevel)
downloadRegionBoundingBox = map.boundingBox
val polygon = Polygon()
polygon.points = Polygon.pointsAsRect(downloadRegionBoundingBox) as MutableList<GeoPoint>
map.overlayManager.add(polygon)
mapController.setZoom(zoomLevel - 1.0)
cacheManager = CacheManager(map)
val tilecount: Int =
cacheManager.possibleTilesInArea(
downloadRegionBoundingBox,
zoomLevelMax.toInt(),
zoomLevelMin.toInt()
)
cacheEstimate.text = ("$tilecount tiles")
}
/**
* if true, start the job
* if false, just update the dialog box
*/
private fun updateEstimate() {
try {
if (this::downloadRegionBoundingBox.isInitialized) {
val outputName =
Configuration.getInstance().osmdroidBasePath.absolutePath + File.separator + "mainFile.sqlite" // TODO: Accept filename input param from user
writer = SqliteArchiveTileWriter(outputName)
//nesw
if (downloadPrompt != null) {
downloadPrompt!!.dismiss()
downloadPrompt = null
}
try {
cacheManager =
CacheManager(map, writer) // Make sure cacheManager has latest from map
} catch (ex: TileSourcePolicyException) {
Log.d("MapFragment", "Tilesource does not allow archiving: ${ex.message}")
return
}
//this triggers the download
downloadRegion(
downloadRegionBoundingBox,
zoomLevelMax.toInt(),
zoomLevelMin.toInt(),
)
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
private fun downloadRegion(bb: BoundingBox, zoommin: Int, zoommax: Int) {
cacheManager.downloadAreaAsync(
activity,
bb,
zoommin,
zoommax,
object : CacheManagerCallback {
override fun onTaskComplete() {
Toast.makeText(activity, "Download complete!", Toast.LENGTH_LONG)
.show()
writer.onDetach()
defaultMapSettings()
}
override fun onTaskFailed(errors: Int) {
Toast.makeText(
activity,
"Download complete with $errors errors",
Toast.LENGTH_LONG
).show()
writer.onDetach()
defaultMapSettings()
}
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
}
})
}
private fun chooseMapStyle() {
@ -111,11 +376,20 @@ class MapFragment : ScreenFragment("Map"), Logging {
editor.apply()
dialog.dismiss()
map.setTileSource(loadOnlineTileSourceBase())
renderDownloadButton()
}
val dialog = builder.create()
dialog.show()
}
private fun renderDownloadButton() {
if (!(map.tileProvider.tileSource as OnlineTileSourceBase).tileSourcePolicy.acceptsBulkDownload()) {
downloadBtn.hide()
} else {
downloadBtn.show()
}
}
private fun onWaypointChanged(wayPt: Collection<Packet>) {
/**
@ -166,8 +440,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
marker.position = GeoPoint(p.latitude, p.longitude)
marker.icon = ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_baseline_location_on_24
requireActivity(), R.drawable.ic_baseline_location_on_24
)
}
marker
@ -177,9 +450,33 @@ class MapFragment : ScreenFragment("Map"), Logging {
nodePositions = getCurrentNodes()
}
/**
* Create LatLong Grid line overlay
* @param enabled: turn on/off gridlines
*/
private fun createLatLongGrid(enabled: Boolean) {
val latLongGridOverlay = LatLonGridlineOverlay2()
latLongGridOverlay.isEnabled = enabled
if (latLongGridOverlay.isEnabled) {
val textPaint = Paint()
textPaint.textSize = 40f
textPaint.color = Color.GRAY
textPaint.isAntiAlias = true
textPaint.isFakeBoldText = true
textPaint.textAlign = Paint.Align.CENTER
latLongGridOverlay.textPaint = textPaint
latLongGridOverlay.setBackgroundColor(Color.TRANSPARENT)
latLongGridOverlay.setLineWidth(3.0f)
latLongGridOverlay.setLineColor(Color.GRAY)
map.overlayManager.add(latLongGridOverlay)
}
}
private fun drawOverlays() {
map.overlayManager.overlays().clear()
addCopyright() // Copyright is required for certain map sources
createLatLongGrid(false)
map.overlayManager.addAll(nodeLayer, nodePositions)
map.overlayManager.addAll(nodeLayer, wayPoints)
map.invalidate()
@ -189,17 +486,19 @@ class MapFragment : ScreenFragment("Map"), Logging {
* Adds copyright to map depending on what source is showing
*/
private fun addCopyright() {
val copyrightNotice: String =
map.tileProvider.tileSource.copyrightNotice
val copyrightOverlay = CopyrightOverlay(context)
copyrightOverlay.setCopyrightNotice(copyrightNotice)
map.overlays.add(copyrightOverlay)
if (map.tileProvider.tileSource.copyrightNotice != null) {
val copyrightNotice: String = map.tileProvider.tileSource.copyrightNotice
val copyrightOverlay = CopyrightOverlay(context)
copyrightOverlay.setCopyrightNotice(copyrightNotice)
map.overlays.add(copyrightOverlay)
}
}
private fun setupMapProperties() {
if (this::map.isInitialized) {
map.setDestroyMode(false) // keeps map instance alive when in the background.
map.isVerticalMapRepetitionEnabled = false // disables map repetition
map.overlayManager = CustomOverlayManager.create(map, context)
map.setScrollableAreaLimitLatitude(
map.overlayManager.tilesOverlay.bounds.actualNorth,
map.overlayManager.tilesOverlay.bounds.actualSouth,
@ -209,8 +508,21 @@ class MapFragment : ScreenFragment("Map"), Logging {
true // scales the map tiles to the display density of the screen
map.minZoomLevel =
defaultMinZoom // sets the minimum zoom level (the furthest out you can zoom)
map.maxZoomLevel = defaultMaxZoom
map.setMultiTouchControls(true) // Sets gesture controls to true.
map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) // Disables default +/- button for zooming
map.addMapListener(object : MapListener {
override fun onScroll(event: ScrollEvent): Boolean {
if (binding.cacheLayout.visibility == View.VISIBLE) {
generateBoxOverlay(zoomLevelMax)
}
return true
}
override fun onZoom(event: ZoomEvent): Boolean {
return false
}
})
}
}
@ -224,8 +536,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
nodesWithPosition.forEach {
points.add(
GeoPoint(
it.position!!.latitude,
it.position!!.longitude
it.position!!.latitude, it.position!!.longitude
)
)
}
@ -249,6 +560,12 @@ class MapFragment : ScreenFragment("Map"), Logging {
override fun onPause() {
map.onPause()
if (alertDialog != null && alertDialog!!.isShowing) {
alertDialog!!.dismiss()
}
if (downloadPrompt != null && downloadPrompt!!.isShowing) {
downloadPrompt!!.dismiss()
}
super.onPause()
}

Wyświetl plik

@ -24,16 +24,99 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cache_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/select_download_region"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/colourGrey"
app:layout_constraintBottom_toTopOf="@id/toggleButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleButton"
style="?attr/materialButtonToggleGroupStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/cache_estimate">
<Button
android:id="@+id/box5miles"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/_5_miles"
app:layout_constraintBottom_toTopOf="@id/cache_estimate"
app:layout_constraintEnd_toStartOf="@id/box10miles"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/box10miles"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/_10_miles"
app:layout_constraintBottom_toTopOf="@id/cache_estimate"
app:layout_constraintEnd_toStartOf="@id/box15miles"
app:layout_constraintStart_toEndOf="@id/box5miles" />
<Button
android:id="@+id/box15miles"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/_15_miles"
app:layout_constraintBottom_toTopOf="@id/cache_estimate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/box10miles" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<TextView
android:id="@+id/cache_estimate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tile_download_estimate"
android:textColor="@color/colourGrey"
app:layout_constraintBottom_toTopOf="@id/executeJob"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/executeJob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_download"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/cancelDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_style_toggle"
android:id="@+id/downloadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/design_default_color_secondary"
android:contentDescription="@string/style_selection"
android:backgroundTint="@color/buttonColor"
android:contentDescription="@string/download_region"
android:orientation="vertical"
android:src="@android:drawable/stat_sys_download"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
tools:background="@color/design_default_color_secondary" />
tools:background="@color/buttonColor" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -89,6 +89,7 @@
<string-array name="map_styles">
<item>OpenStreetMap</item>
<item>USGS TOPO</item>
<item>Open TOPO</item>
<item>USGS Satellite</item>
<item>ESRI World Overview</item>
</string-array>

Wyświetl plik

@ -157,4 +157,10 @@
<string name="direct_message">Direct Message</string>
<string name="nodedb_reset">NodeDB reset</string>
<string name="nodedb_reset_description">This will clear all nodes from this list.</string>
<string name="select_download_region">Select download region</string>
<string name="_5_miles">5 Miles</string>
<string name="_10_miles">10 miles</string>
<string name="_15_miles">15 miles</string>
<string name="tile_download_estimate">Tile download estimate:</string>
<string name="start_download">Start Download</string>
</resources>