diff --git a/app/build.gradle b/app/build.gradle index a9e2201b..e37c202a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,7 +143,8 @@ dependencies { //OSMDROID, mgrs, implementation 'org.osmdroid:osmdroid-android:6.1.14' implementation 'com.github.MKergall:osmbonuspack:6.9.0' - implementation 'mil.nga:mgrs:2.0.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 diff --git a/app/src/main/java/com/geeksville/mesh/model/CustomTileSource.kt b/app/src/main/java/com/geeksville/mesh/model/CustomTileSource.kt index e7b68011..030c3408 100644 --- a/app/src/main/java/com/geeksville/mesh/model/CustomTileSource.kt +++ b/app/src/main/java/com/geeksville/mesh/model/CustomTileSource.kt @@ -5,19 +5,21 @@ import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.tileprovider.tilesource.TileSourcePolicy import org.osmdroid.util.MapTileIndex +import org.osmdroid.wms.WMSTileSource 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", + // 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( - 2, TileSourcePolicy.FLAG_NO_BULK + 2, + TileSourcePolicy.FLAG_NO_BULK or TileSourcePolicy.FLAG_NO_PREVENTIVE or TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL or TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED @@ -30,16 +32,80 @@ class CustomTileSource { + mImageFilenameEnding) } } - val MAPNIK: OnlineTileSourceBase = TileSourceFactory.MAPNIK - val USGS_TOPO: OnlineTileSourceBase = TileSourceFactory.USGS_TOPO - val USGS_SAT: OnlineTileSourceBase = TileSourceFactory.USGS_SAT + //https://nowcoast.noaa.gov/arcgis/rest/services/nowcoast/radar_meteo_imagery_nexrad_time/MapServer + + 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_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 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 = - listOf(MAPNIK, USGS_TOPO, USGS_SAT, ESRI_IMAGERY) + listOf( + MAPNIK, + USGS_TOPO, + USGS_SAT, + ESRI_IMAGERY, + NOAA_RADAR + ) fun getTileSource(aName: String): ITileSource { diff --git a/app/src/main/java/com/geeksville/mesh/model/NOAAWmsTileSource.kt b/app/src/main/java/com/geeksville/mesh/model/NOAAWmsTileSource.kt new file mode 100644 index 00000000..da5f65b5 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/model/NOAAWmsTileSource.kt @@ -0,0 +1,146 @@ +package com.geeksville.mesh.model + +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, + 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() + } +} \ 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 d54dfa34..769a9e4f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -8,7 +8,6 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.os.Bundle -import android.os.Environment import android.text.Editable import android.text.TextWatcher import android.util.Log @@ -264,7 +263,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener, OnSeek if (startJob) { val outputName = - Configuration.getInstance().osmdroidBasePath.absolutePath + File.separator + "outputName.sqlite" + Configuration.getInstance().osmdroidBasePath.absolutePath + File.separator + "mainFile.sqlite" // TODO: Accept filename input param from user writer = SqliteArchiveTileWriter(outputName) try { cacheManager = CacheManager(map, writer) @@ -465,10 +464,12 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener, OnSeek * 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() { diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 38fea1c7..742af2ee 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -91,5 +91,6 @@ USGS TOPO USGS Satellite ESRI World Overview + NOAA GOES Radar \ No newline at end of file