mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: maps (#2097)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
c05f434ff2
commit
87e50e03ea
76 changed files with 4188 additions and 1830 deletions
71
app/src/fdroid/java/com/geeksville/mesh/util/MapUtils.kt
Normal file
71
app/src/fdroid/java/com/geeksville/mesh/util/MapUtils.kt
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
|
||||
import org.osmdroid.util.BoundingBox
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
|
||||
private const val DEGREES_IN_CIRCLE = 360.0
|
||||
private const val METERS_PER_DEGREE_LATITUDE = 111320.0
|
||||
private const val ZOOM_ADJUSTMENT_FACTOR = 0.8
|
||||
|
||||
/**
|
||||
* Calculates the zoom level required to fit the entire [BoundingBox] inside the map view.
|
||||
*
|
||||
* @return The zoom level as a Double value.
|
||||
*/
|
||||
fun BoundingBox.requiredZoomLevel(): Double {
|
||||
val topLeft = GeoPoint(this.latNorth, this.lonWest)
|
||||
val bottomRight = GeoPoint(this.latSouth, this.lonEast)
|
||||
val latLonWidth = topLeft.distanceToAsDouble(GeoPoint(topLeft.latitude, bottomRight.longitude))
|
||||
val latLonHeight = topLeft.distanceToAsDouble(GeoPoint(bottomRight.latitude, topLeft.longitude))
|
||||
val requiredLatZoom = log2(DEGREES_IN_CIRCLE / (latLonHeight / METERS_PER_DEGREE_LATITUDE))
|
||||
val requiredLonZoom = log2(DEGREES_IN_CIRCLE / (latLonWidth / METERS_PER_DEGREE_LATITUDE))
|
||||
return maxOf(requiredLatZoom, requiredLonZoom) * ZOOM_ADJUSTMENT_FACTOR
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new bounding box with adjusted dimensions based on the provided [zoomFactor].
|
||||
*
|
||||
* @return A new [BoundingBox] with added [zoomFactor]. Example:
|
||||
* ```
|
||||
* // Setting the zoom level directly using setZoom()
|
||||
* map.setZoom(14.0)
|
||||
* val boundingBoxZoom14 = map.boundingBox
|
||||
*
|
||||
* // Using zoomIn() results the equivalent BoundingBox with setZoom(15.0)
|
||||
* val boundingBoxZoom15 = boundingBoxZoom14.zoomIn(1.0)
|
||||
* ```
|
||||
*/
|
||||
fun BoundingBox.zoomIn(zoomFactor: Double): BoundingBox {
|
||||
val center = GeoPoint((latNorth + latSouth) / 2, (lonWest + lonEast) / 2)
|
||||
val latDiff = latNorth - latSouth
|
||||
val lonDiff = lonEast - lonWest
|
||||
|
||||
val newLatDiff = latDiff / (2.0.pow(zoomFactor))
|
||||
val newLonDiff = lonDiff / (2.0.pow(zoomFactor))
|
||||
|
||||
return BoundingBox(
|
||||
center.latitude + newLatDiff / 2,
|
||||
center.longitude + newLonDiff / 2,
|
||||
center.latitude - newLatDiff / 2,
|
||||
center.longitude - newLonDiff / 2,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.DashPathEffect
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.R
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.MapView
|
||||
import org.osmdroid.views.overlay.CopyrightOverlay
|
||||
import org.osmdroid.views.overlay.Marker
|
||||
import org.osmdroid.views.overlay.Polyline
|
||||
import org.osmdroid.views.overlay.ScaleBarOverlay
|
||||
import org.osmdroid.views.overlay.advancedpolyline.MonochromaticPaintList
|
||||
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
|
||||
|
||||
/** Adds copyright to map depending on what source is showing */
|
||||
fun MapView.addCopyright() {
|
||||
if (overlays.none { it is CopyrightOverlay }) {
|
||||
val copyrightNotice: String = tileProvider.tileSource.copyrightNotice ?: return
|
||||
val copyrightOverlay = CopyrightOverlay(context)
|
||||
copyrightOverlay.setCopyrightNotice(copyrightNotice)
|
||||
overlays.add(copyrightOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create LatLong Grid line overlay
|
||||
*
|
||||
* @param enabled: turn on/off gridlines
|
||||
*/
|
||||
fun MapView.createLatLongGrid(enabled: Boolean) {
|
||||
val latLongGridOverlay = LatLonGridlineOverlay2()
|
||||
latLongGridOverlay.isEnabled = enabled
|
||||
if (latLongGridOverlay.isEnabled) {
|
||||
val textPaint =
|
||||
Paint().apply {
|
||||
textSize = 40f
|
||||
color = Color.GRAY
|
||||
isAntiAlias = true
|
||||
isFakeBoldText = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
latLongGridOverlay.textPaint = textPaint
|
||||
latLongGridOverlay.setBackgroundColor(Color.TRANSPARENT)
|
||||
latLongGridOverlay.setLineWidth(3.0f)
|
||||
latLongGridOverlay.setLineColor(Color.GRAY)
|
||||
overlays.add(latLongGridOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
fun MapView.addScaleBarOverlay(density: Density) {
|
||||
if (overlays.none { it is ScaleBarOverlay }) {
|
||||
val scaleBarOverlay =
|
||||
ScaleBarOverlay(this).apply {
|
||||
setAlignBottom(true)
|
||||
with(density) {
|
||||
setScaleBarOffset(15.dp.toPx().toInt(), 40.dp.toPx().toInt())
|
||||
setTextSize(12.sp.toPx())
|
||||
}
|
||||
textPaint.apply {
|
||||
isAntiAlias = true
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
}
|
||||
}
|
||||
overlays.add(scaleBarOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
fun MapView.addPolyline(density: Density, geoPoints: List<GeoPoint>, onClick: () -> Unit): Polyline {
|
||||
val polyline =
|
||||
Polyline(this).apply {
|
||||
val borderPaint =
|
||||
Paint().apply {
|
||||
color = Color.BLACK
|
||||
isAntiAlias = true
|
||||
strokeWidth = with(density) { 10.dp.toPx() }
|
||||
style = Paint.Style.STROKE
|
||||
strokeJoin = Paint.Join.ROUND
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
pathEffect = DashPathEffect(floatArrayOf(80f, 60f), 0f)
|
||||
}
|
||||
outlinePaintLists.add(MonochromaticPaintList(borderPaint))
|
||||
val fillPaint =
|
||||
Paint().apply {
|
||||
color = Color.WHITE
|
||||
isAntiAlias = true
|
||||
strokeWidth = with(density) { 6.dp.toPx() }
|
||||
style = Paint.Style.FILL_AND_STROKE
|
||||
strokeJoin = Paint.Join.ROUND
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
pathEffect = DashPathEffect(floatArrayOf(80f, 60f), 0f)
|
||||
}
|
||||
outlinePaintLists.add(MonochromaticPaintList(fillPaint))
|
||||
setPoints(geoPoints)
|
||||
setOnClickListener { _, _, _ ->
|
||||
onClick()
|
||||
true
|
||||
}
|
||||
}
|
||||
overlays.add(polyline)
|
||||
|
||||
return polyline
|
||||
}
|
||||
|
||||
fun MapView.addPositionMarkers(positions: List<MeshProtos.Position>, onClick: () -> Unit): List<Marker> {
|
||||
val navIcon = ContextCompat.getDrawable(context, R.drawable.ic_map_navigation_24)
|
||||
val markers =
|
||||
positions.map {
|
||||
Marker(this).apply {
|
||||
icon = navIcon
|
||||
rotation = (it.groundTrack * 1e-5).toFloat()
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||
position = GeoPoint(it.latitudeI * 1e-7, it.longitudeI * 1e-7)
|
||||
setOnMarkerClickListener { _, _ ->
|
||||
onClick()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
overlays.addAll(markers)
|
||||
|
||||
return markers
|
||||
}
|
||||
111
app/src/fdroid/java/com/geeksville/mesh/util/SqlTileWriterExt.kt
Normal file
111
app/src/fdroid/java/com/geeksville/mesh/util/SqlTileWriterExt.kt
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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? = 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<SourceCount>
|
||||
get() {
|
||||
val db = db
|
||||
val ret: MutableList<SourceCount> = 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_EXPIRES<?", arrayOf(System.currentTimeMillis().toString()))
|
||||
|
||||
class SourceCount {
|
||||
var rowCount: Long = 0
|
||||
var source: String? = null
|
||||
var sizeTotal: Long = 0
|
||||
var sizeMin: Long = 0
|
||||
var sizeMax: Long = 0
|
||||
var sizeAvg: Long = 0
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue