sforkowany z mirror/meshtastic-android
commit
e3b12fc74c
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Ładowanie…
Reference in New Issue