diff --git a/app/src/main/java/com/geeksville/mesh/model/map/CustomTileSource.kt b/app/src/main/java/com/geeksville/mesh/model/map/CustomTileSource.kt index d9b9e5ee..bcfaecd1 100644 --- a/app/src/main/java/com/geeksville/mesh/model/map/CustomTileSource.kt +++ b/app/src/main/java/com/geeksville/mesh/model/map/CustomTileSource.kt @@ -8,13 +8,47 @@ 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 + companion object { + val OPENWEATHER_RADAR = OnlineTileSourceAuth( + "Open Weather Map", 1, 22, 256, ".png", arrayOf( + "https://tile.openweathermap.org/map/" + ), "Openweathermap", + TileSourcePolicy( + 4, + TileSourcePolicy.FLAG_NO_BULK + or TileSourcePolicy.FLAG_NO_PREVENTIVE + or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL + or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED + ), + "precipitation", + "" + ) + // +// val RAIN_VIEWER = object : OnlineTileSourceBase( +// "RainViewer", 1, 15, 256, ".png", arrayOf( +// "https://tilecache.rainviewer.com/v2/coverage/" +// ), "RainViewer", +// 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) +// } +// } + + 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 World Overview", 1, 20, 256, ".jpg", arrayOf( + "https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/" ), "Esri, Maxar, Earthstar Geographics, and the GIS User Community", TileSourcePolicy( 4, @@ -32,20 +66,18 @@ class CustomTileSource { } } - //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, + private val ESRI_WORLD_TOPO = object : OnlineTileSourceBase( + "ESRI World TOPO", + 1, + 20, 256, - "", + ".jpg", arrayOf( - "https://earthlive.maptiles.arcgis.com/arcgis/rest/services/GOES/GOES31C/MapServer/tile/" + "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/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", + "Esri, HERE, Garmin, FAO, NOAA, USGS, © OpenStreetMap contributors, and the GIS User Community ", TileSourcePolicy( - 2, + 4, TileSourcePolicy.FLAG_NO_BULK or TileSourcePolicy.FLAG_NO_PREVENTIVE or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL @@ -116,18 +148,7 @@ class CustomTileSource { "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", + "1.1.0", "", "EPSG%3A3857", "", @@ -153,6 +174,7 @@ class CustomTileSource { MAPNIK, USGS_TOPO, OPEN_TOPO, + ESRI_WORLD_TOPO, USGS_SAT, ESRI_IMAGERY, ) diff --git a/app/src/main/java/com/geeksville/mesh/model/map/NOAAWmsTileSource.kt b/app/src/main/java/com/geeksville/mesh/model/map/NOAAWmsTileSource.kt index 434a929e..a99cf1f9 100644 --- a/app/src/main/java/com/geeksville/mesh/model/map/NOAAWmsTileSource.kt +++ b/app/src/main/java/com/geeksville/mesh/model/map/NOAAWmsTileSource.kt @@ -4,6 +4,7 @@ import android.content.res.Resources import android.util.Log import org.osmdroid.api.IMapView import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase +import org.osmdroid.tileprovider.tilesource.TileSourcePolicy import org.osmdroid.util.MapTileIndex import kotlin.math.atan import kotlin.math.pow @@ -15,10 +16,18 @@ open class NOAAWmsTileSource( layername: String, version: String, time: String?, - crs: String, + srs: String, style: String?, - format: String -) : OnlineTileSourceBase(aName, 0, 22, 256, "png", aBaseUrl) { + format: String, +) : OnlineTileSourceBase( + aName, 0, 5, 256, "png", aBaseUrl, "", TileSourcePolicy( + 2, + TileSourcePolicy.FLAG_NO_BULK + or TileSourcePolicy.FLAG_NO_PREVENTIVE + or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL + or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED + ) +) { // array indexes for array to hold bounding boxes. private val MINX = 0 @@ -37,8 +46,7 @@ open class NOAAWmsTileSource( 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 srs = "EPSG%3A3857" //used by geo server private var format = "" private var time = "" private var style: String? = null @@ -49,7 +57,7 @@ open class NOAAWmsTileSource( Log.i(IMapView.LOGTAG, "WMS support is BETA. Please report any issues") layer = layername this.version = version - this.crs = crs + this.srs = srs this.style = style this.format = format if (time != null) this.time = time @@ -128,7 +136,8 @@ open class NOAAWmsTileSource( 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("&srs=").append(srs) + sb.append("&size=").append(getSize()) sb.append("&bbox=") val bbox = getBoundingBox( MapTileIndex.getX(pMapTileIndex), @@ -139,8 +148,14 @@ open class NOAAWmsTileSource( sb.append(bbox[MINY]).append(",") sb.append(bbox[MAXX]).append(",") sb.append(bbox[MAXY]) - Log.i(IMapView.LOGTAG, sb.toString()) return sb.toString() } + + private fun getSize(): String { + val height = Resources.getSystem().displayMetrics.heightPixels + val width = Resources.getSystem().displayMetrics.widthPixels + return "$width,$height" + + } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/map/OnlineTileSourceAuth.kt b/app/src/main/java/com/geeksville/mesh/model/map/OnlineTileSourceAuth.kt new file mode 100644 index 00000000..1e4e37a3 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/model/map/OnlineTileSourceAuth.kt @@ -0,0 +1,47 @@ +package com.geeksville.mesh.model.map + +import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase +import org.osmdroid.tileprovider.tilesource.TileSourcePolicy +import org.osmdroid.util.MapTileIndex + +open class OnlineTileSourceAuth( + aName: String, + aZoomLevel: Int, + aZoomMaxLevel: Int, + aTileSizePixels: Int, + aImageFileNameEnding: String, + aBaseUrl: Array, + pCopyright: String, + tileSourcePolicy: TileSourcePolicy, + layerName: String?, + apiKey: String +) : + OnlineTileSourceBase( + aName, + aZoomLevel, + aZoomMaxLevel, + aTileSizePixels, + aImageFileNameEnding, + aBaseUrl, + pCopyright, + tileSourcePolicy + + ) { + private var layerName = "" + private var apiKey = "" + + init { + if (layerName != null) { + this.layerName = layerName + } + this.apiKey = apiKey + + } + + override fun getTileURLString(pMapTileIndex: Long): String { + return "$baseUrl$layerName/" + (MapTileIndex.getZoom(pMapTileIndex) + .toString() + "/" + MapTileIndex.getX(pMapTileIndex) + .toString() + "/" + MapTileIndex.getY(pMapTileIndex) + .toString()) + mImageFilenameEnding + "?appId=$apiKey" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 5bb19681..46df1652 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -22,6 +22,7 @@ import com.geeksville.mesh.databinding.MapViewBinding 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.SqlTileWriterExt import com.geeksville.mesh.util.formatAgo import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint @@ -30,9 +31,9 @@ import org.osmdroid.config.Configuration import org.osmdroid.events.MapListener import org.osmdroid.events.ScrollEvent import org.osmdroid.events.ZoomEvent +import org.osmdroid.tileprovider.MapTileProviderBasic 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 @@ -57,6 +58,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { private lateinit var executeJob: Button private var downloadPrompt: AlertDialog? = null private var alertDialog: AlertDialog? = null + private var cache: SqlTileWriterExt? = null // constants private val defaultMinZoom = 1.5 @@ -85,7 +87,6 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { private lateinit var cacheManager: CacheManager private lateinit var downloadRegionBoundingBox: BoundingBox - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { @@ -165,7 +166,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { downloadJobAlert() dialog.dismiss() } - 2 -> clearCache() + 2 -> purgeTileSource() else -> dialog.dismiss() } } @@ -177,16 +178,48 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { } - /** - * 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 purgeTileSource() { + cache = SqlTileWriterExt() + val builder = AlertDialog.Builder(context) + builder.setTitle("Tile Source") + val sources = cache!!.sources + val sourceList = mutableListOf() + for (i in sources.indices) { + sourceList.add(sources[i].source as String) + } + val selected: BooleanArray? = null + val selectedList = mutableListOf() + builder.setMultiChoiceItems( + sourceList.toTypedArray(), + selected + ) { _, i, b -> + if (b) { + selectedList.add(i) + } else { + selectedList.remove(i) + } + + } + builder.setPositiveButton("Clear") { _, _ -> + for (x in selectedList) { + val item = sources[x] + val b = cache!!.purgeCache(item.source) + if (b) Toast.makeText( + context, + "SQL Cache purged for ${item.source}", + Toast.LENGTH_SHORT + ) + .show() else Toast.makeText( + context, + "SQL Cache purge failed, see logcat for details", + Toast.LENGTH_LONG + ).show() + } + } + builder.setNegativeButton( + "Cancel" + ) { dialog, _ -> dialog.cancel() } + builder.show() } @@ -357,22 +390,20 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { private fun chooseMapStyle() { /// Prepare dialog and its items - val builder = MaterialAlertDialogBuilder(context!!) - builder.setTitle(getString(R.string.preferences_map_style)) val mapStyles by lazy { resources.getStringArray(R.array.map_styles) } + val builder = MaterialAlertDialogBuilder(context!!) /// Load preferences and its value - val editor: SharedPreferences.Editor = mPrefs.edit() val mapStyleInt = mPrefs.getInt(mapStyleId, 1) - debug("mapStyleId from prefs: $mapStyleInt") - builder.setSingleChoiceItems(mapStyles, mapStyleInt) { dialog, which -> debug("Set mapStyleId pref to $which") + val editor: SharedPreferences.Editor = mPrefs.edit() editor.putInt(mapStyleId, which) editor.apply() dialog.dismiss() map.setTileSource(loadOnlineTileSourceBase()) renderDownloadButton() + drawOverlays() } val dialog = builder.create() dialog.show() @@ -478,6 +509,22 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { map.invalidate() } +// private fun addWeatherLayer() { + // if (map.tileProvider.tileSource.name() +// .equals(CustomTileSource.getTileSource("ESRI World TOPO").name()) +// ) { +// val layer = TilesOverlay( +// MapTileProviderBasic( +// activity, +// CustomTileSource.OPENWEATHER_RADAR +// ), context +// ) +// layer.loadingBackgroundColor = Color.TRANSPARENT +// layer.loadingLineColor = Color.TRANSPARENT +// map.overlayManager.add(layer) +// } +// } + /** * Adds copyright to map depending on what source is showing */ diff --git a/app/src/main/java/com/geeksville/mesh/util/SqlTileWriterExt.kt b/app/src/main/java/com/geeksville/mesh/util/SqlTileWriterExt.kt new file mode 100644 index 00000000..edeea830 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/SqlTileWriterExt.kt @@ -0,0 +1,81 @@ +package com.geeksville.mesh.util + +import android.database.Cursor +import org.osmdroid.tileprovider.modules.DatabaseFileArchive +import org.osmdroid.tileprovider.modules.SqlTileWriter + + +/** + * Extended the sqlite tile writer to have some additional query functions. A this point + * it's unclear if there is a need to put these with the osmdroid-android library, thus they were + * put here as more of an example. + * + * + * created on 12/21/2016. + * + * @author Alex O'Ree + * @since 5.6.2 + */ +class SqlTileWriterExt() : SqlTileWriter() { + fun select(rows: Int, offset: Int): Cursor? { + return this.db?.rawQuery( + "select " + DatabaseFileArchive.COLUMN_KEY + "," + COLUMN_EXPIRES + "," + DatabaseFileArchive.COLUMN_PROVIDER + " from " + DatabaseFileArchive.TABLE + " limit ? offset ?", + arrayOf(rows.toString() + "", offset.toString() + "") + ) + } + + /** + * gets all the tiles sources that we have tiles for in the cache database and their counts + * + * @return + */ + val sources: List + get() { + val db = db + val ret: MutableList = ArrayList() + if (db == null) { + return ret + } + var cur: Cursor? = null + try { + cur = db.rawQuery( + "select " + + DatabaseFileArchive.COLUMN_PROVIDER + + ",count(*) " + + ",min(length(" + DatabaseFileArchive.COLUMN_TILE + ")) " + + ",max(length(" + DatabaseFileArchive.COLUMN_TILE + ")) " + + ",sum(length(" + DatabaseFileArchive.COLUMN_TILE + ")) " + + "from " + DatabaseFileArchive.TABLE + " " + + "group by " + DatabaseFileArchive.COLUMN_PROVIDER, null + ) + while (cur.moveToNext()) { + val c = SourceCount() + c.source = cur.getString(0) + c.rowCount = cur.getLong(1) + c.sizeMin = cur.getLong(2) + c.sizeMax = cur.getLong(3) + c.sizeTotal = cur.getLong(4) + c.sizeAvg = c.sizeTotal / c.rowCount + ret.add(c) + } + } catch (e: Exception) { + catchException(e) + } finally { + cur?.close() + } + return ret + } + val rowCountExpired: Long + get() = getRowCount( + "$COLUMN_EXPIRESOpenStreetMap USGS TOPO Open TOPO + ESRI World TOPO USGS Satellite ESRI World Overview