kopia lustrzana https://github.com/ge0rg/aprsdroid
Rectify bug in StorageDatabase pertaining to columns in Station table as this causes issues if the table is extended
Add in ability to parse weather station data and present it in a more friendly human readable formatpull/159/head
rodzic
e17b20bdc3
commit
47d72f7d48
|
@ -47,6 +47,15 @@
|
|||
android:typeface="monospace"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/extendedinfo"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#8080b0"
|
||||
android:textSize="17dp"
|
||||
android:typeface="monospace"
|
||||
android:focusable="false"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/listmessage"
|
||||
android:layout_width="fill_parent"
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.aprsdroid.app
|
|||
|
||||
import _root_.android.location.Location
|
||||
import _root_.net.ab0oo.aprs.parser._
|
||||
import scala.util.matching
|
||||
|
||||
object AprsPacket {
|
||||
val QRG_RE = ".*?(\\d{2,3}[.,]\\d{3,4}).*?".r
|
||||
|
@ -36,6 +37,12 @@ object AprsPacket {
|
|||
|
||||
def mps2kt(mps : Double) : Int = (mps*1.94384449).asInstanceOf[Int]
|
||||
|
||||
def kt2mps(kt : Double) : Int = (kt/1.94384449).asInstanceOf[Int]
|
||||
|
||||
def hi2mm(hi : Double) : Double = (hi*0.254)
|
||||
|
||||
def f2c(f : Double) : Double = (f - 32d) * 5d / 9d
|
||||
|
||||
def formatAltitude(location : Location) : String = {
|
||||
if (location.hasAltitude)
|
||||
"/A=%06d".format(m2ft(location.getAltitude))
|
||||
|
@ -109,6 +116,96 @@ object AprsPacket {
|
|||
}
|
||||
}
|
||||
|
||||
val WX_PEET_RE = "^\\$ULTW".r
|
||||
def parseWxPeet(comment : String) : String = {
|
||||
import StationListAdapter._
|
||||
""
|
||||
}
|
||||
|
||||
|
||||
def parseWxElement(element : String, expr : scala.util.matching.Regex, grp : Int) : Option[Double] = {
|
||||
var retval = expr.findFirstMatchIn(element).map(_ group grp).getOrElse(null)
|
||||
try { Some(retval.toDouble) } catch { case _ => None }
|
||||
}
|
||||
|
||||
def parseWx(comment : String) : String = {
|
||||
import StationListAdapter._
|
||||
// Sample
|
||||
// 000/000g000t056r000p000P000h75b10164L000eCumulusDsVP
|
||||
//val comment = "000/000g000t056r000p000P000h75b10164L000eCumulusDsVP"
|
||||
|
||||
// Handle PEET events
|
||||
if(comment.matches(WX_PEET_RE.toString)) return parseWxPeet(comment)
|
||||
|
||||
val WX_WIND_RE = ".*(\\d{3})/(\\d{3}).*".r
|
||||
val WX_GUST_RE = ".*g(\\d{3}).*".r
|
||||
val WX_SPEED_RE = ".*s(\\d{3}).*".r
|
||||
val WX_DIR_RE = ".*c(\\d{3}).*".r
|
||||
val WX_TEMP_RE = ".*t(\\d{3}).*".r
|
||||
val WX_RAIN_HOUR_RE = ".*r(\\d{3}).*".r
|
||||
val WX_RAIN_24_RE = ".*p(\\d{3}).*".r
|
||||
val WX_RAIN_TODAY_RE = ".*P(\\d{3}).*".r
|
||||
val WX_HUMID_RE = ".*h(\\d{2}).*".r
|
||||
val WX_QNH_RE = ".*b(\\d{5}).*".r
|
||||
val WX_LUM_LOW_RE = ".*L(\\d{3}).*".r
|
||||
val WX_LUM_HIGH_RE = ".*l(\\d{4}).*".r
|
||||
val WX_SNOW = ".*s(\\d{3}).*".r
|
||||
|
||||
var windDir = parseWxElement(comment, WX_WIND_RE, 1)
|
||||
var windSpeed = parseWxElement(comment, WX_WIND_RE, 2)
|
||||
var windGust = parseWxElement(comment, WX_GUST_RE, 1)
|
||||
var snow = parseWxElement(comment, WX_SNOW, 1)
|
||||
if (windSpeed == None) {
|
||||
// For some reason, snow and windspeed use the same prefix. If we didn't get wind using the
|
||||
// XXX/XXX format, then it must be here as sXXX wind speed,
|
||||
// therefore snow is not possible to get in this situation
|
||||
snow = None
|
||||
windSpeed = parseWxElement(comment, WX_SPEED_RE, 1)
|
||||
}
|
||||
if (windDir == None) windDir = parseWxElement(comment, WX_DIR_RE, 1)
|
||||
var ambTemp = parseWxElement(comment, WX_TEMP_RE, 1)
|
||||
var rainHour = parseWxElement(comment, WX_RAIN_HOUR_RE, 1)
|
||||
var rainDay = parseWxElement(comment, WX_RAIN_24_RE, 1)
|
||||
var rainToday = parseWxElement(comment, WX_RAIN_TODAY_RE, 1)
|
||||
var humidity = parseWxElement(comment, WX_HUMID_RE, 1)
|
||||
var qnh = parseWxElement(comment, WX_QNH_RE, 1)
|
||||
var lumLow = parseWxElement(comment, WX_LUM_LOW_RE, 1)
|
||||
var lumHigh = parseWxElement(comment, WX_LUM_HIGH_RE, 1)
|
||||
|
||||
if(lumHigh != None)
|
||||
lumHigh = Some(lumHigh.get + 1000.0)
|
||||
|
||||
var lum = if(lumLow != None) lumLow else if(lumHigh != None) lumHigh else None
|
||||
|
||||
// unit conversions
|
||||
windSpeed = if (windSpeed != None) Some(kt2mps(windSpeed.get)) else None
|
||||
windGust = if (windGust != None) Some(kt2mps(windGust.get)) else None
|
||||
ambTemp = if (ambTemp != None) Some(f2c(ambTemp.get)) else None
|
||||
rainHour = if(rainHour != None) Some(hi2mm(rainHour.get)) else None
|
||||
rainDay = if(rainDay != None) Some(hi2mm(rainDay.get)) else None
|
||||
rainToday = if(rainToday != None) Some(hi2mm(rainToday.get)) else None
|
||||
snow = if(snow != None) Some(hi2mm(snow.get)) else None
|
||||
var compWind = if (windDir != None) getBearing(windDir.get) else None
|
||||
|
||||
val wxFinal = new scala.collection.mutable.ListBuffer[String]
|
||||
if(ambTemp != None) wxFinal += "%1.1f°C".format(ambTemp.get)
|
||||
if(humidity != None) wxFinal += "%1.0f%%".format(humidity.get)
|
||||
val windBuf = new scala.collection.mutable.ListBuffer[String]
|
||||
if(windSpeed != None) windBuf += "Wind %1.1fm/s".format(windSpeed.get)
|
||||
if(windGust != None) windBuf += "Gust %1.1fm/s".format(windGust.get)
|
||||
if(windDir != None) windBuf += "%s (%1.0f°)".format(compWind, windDir.get)
|
||||
if(windBuf.length > 0) wxFinal += windBuf.toList.mkString(" ")
|
||||
if(qnh != None) wxFinal += "%1.0fmbar".format(qnh.get)
|
||||
val rainBuf = new scala.collection.mutable.ListBuffer[String]
|
||||
if(rainHour != None) rainBuf += "%1.1f".format(rainHour.get)
|
||||
if(rainDay != None) rainBuf += "%1.1f".format(rainHour.get)
|
||||
if(rainToday != None) rainBuf += "%1.1f".format(rainHour.get)
|
||||
if(rainBuf.length > 0) wxFinal += "Rain " + rainBuf.toList.mkString("/") + "mm"
|
||||
if(snow != None) wxFinal += "Snow %1.1fmm".format(snow.get)
|
||||
if(lum != None) wxFinal += "%1.0fW/m2".format(lum.get)
|
||||
wxFinal.toList.mkString(" ")
|
||||
}
|
||||
|
||||
def parseHostPort(hostport : String, defaultport : Int) : (String, Int) = {
|
||||
val splits = hostport.trim().split(":")
|
||||
try {
|
||||
|
|
|
@ -12,12 +12,15 @@ import _root_.android.widget.FilterQueryProvider
|
|||
|
||||
object StationListAdapter {
|
||||
import StorageDatabase.Station._
|
||||
val LIST_FROM = Array(CALL, COMMENT, QRG)
|
||||
val LIST_TO = Array(R.id.station_call, R.id.listmessage, R.id.station_qrg)
|
||||
val LIST_FROM = Array(CALL, COMMENT, QRG, WX)
|
||||
val LIST_TO = Array(R.id.station_call, R.id.listmessage, R.id.station_qrg, R.id.extendedinfo)
|
||||
|
||||
val SINGLE = 0
|
||||
val NEIGHBORS = 1
|
||||
val SSIDS = 2
|
||||
// return compass bearing for a given value
|
||||
private val LETTERS = Array("N", "NE", "E", "SE", "S", "SW", "W", "NW")
|
||||
def getBearing(b : Double) = LETTERS(((b.toInt + 22 + 720) % 360) / 45)
|
||||
}
|
||||
|
||||
class StationListAdapter(context : Context, prefs : PrefsWrapper,
|
||||
|
@ -52,10 +55,6 @@ class StationListAdapter(context : Context, prefs : PrefsWrapper,
|
|||
mix.reduceLeft(_*256 + _)
|
||||
}
|
||||
|
||||
// return compass bearing for a given value
|
||||
private val LETTERS = Array("N", "NE", "E", "SE", "S", "SW", "W", "NW")
|
||||
def getBearing(b : Double) = LETTERS(((b.toInt + 22 + 720) % 360) / 45)
|
||||
|
||||
override def bindView(view : View, context : Context, cursor : Cursor) {
|
||||
import StorageDatabase.Station._
|
||||
|
||||
|
@ -67,9 +66,9 @@ class StationListAdapter(context : Context, prefs : PrefsWrapper,
|
|||
val lat = cursor.getInt(COLUMN_LAT)
|
||||
val lon = cursor.getInt(COLUMN_LON)
|
||||
val qrg = cursor.getString(COLUMN_QRG)
|
||||
var wx = cursor.getString(COLUMN_WX)
|
||||
val symbol = cursor.getString(COLUMN_SYMBOL)
|
||||
val dist = Array[Float](0, 0)
|
||||
|
||||
if (call == mycall) {
|
||||
view.setBackgroundColor(0x4020ff20)
|
||||
} else if (call == targetcall) {
|
||||
|
@ -85,8 +84,10 @@ class StationListAdapter(context : Context, prefs : PrefsWrapper,
|
|||
val MCD = 1000000.
|
||||
android.location.Location.distanceBetween(my_lat/MCD, my_lon/MCD,
|
||||
lat/MCD, lon/MCD, dist)
|
||||
distage.setText("%1.1f km %s\n%s".format(dist(0)/1000., getBearing(dist(1)), age))
|
||||
distage.setText("%1.1f km %s\n%s".format(dist(0)/1000., StationListAdapter.getBearing(dist(1)), age))
|
||||
view.findViewById(R.id.station_symbol).asInstanceOf[SymbolView].setSymbol(symbol)
|
||||
val wx_visible = if(wx != null && wx != "") View.VISIBLE else View.GONE
|
||||
view.findViewById(R.id.extendedinfo).setVisibility(wx_visible)
|
||||
super.bindView(view, context, cursor)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import _root_.scala.math.{cos, Pi}
|
|||
|
||||
object StorageDatabase {
|
||||
val TAG = "APRSdroid.Storage"
|
||||
val DB_VERSION = 3
|
||||
val DB_VERSION = 4
|
||||
val DB_NAME = "storage.db"
|
||||
|
||||
val TSS_COL = "DATETIME(TS/1000, 'unixepoch', 'localtime') as TSS"
|
||||
|
@ -57,30 +57,32 @@ object StorageDatabase {
|
|||
val ORIGIN = "origin" // originator call for object/item
|
||||
val QRG = "qrg" // voice frequency
|
||||
val FLAGS = "flags" // bitmask for attributes like "messaging capable"
|
||||
val WX = "wx" // weather data
|
||||
lazy val TABLE_CREATE = """CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s LONG,
|
||||
%s TEXT UNIQUE, %s INTEGER, %s INTEGER,
|
||||
%s INTEGER, %s INTEGER, %s INTEGER,
|
||||
%s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER)"""
|
||||
%s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s TEXT)"""
|
||||
.format(TABLE, _ID, TS,
|
||||
CALL, LAT, LON,
|
||||
SPEED, COURSE, ALT,
|
||||
SYMBOL, COMMENT, ORIGIN, QRG, FLAGS)
|
||||
SYMBOL, COMMENT, ORIGIN, QRG, FLAGS, WX)
|
||||
lazy val TABLE_DROP = "DROP TABLE %s".format(TABLE)
|
||||
lazy val COLUMNS = Array(_ID, TS, CALL, LAT, LON, SYMBOL, COMMENT, SPEED, COURSE, ALT, ORIGIN, QRG)
|
||||
lazy val COLUMNS = Array(_ID, TS, CALL, LAT, LON, SPEED, COURSE, ALT, SYMBOL, COMMENT, ORIGIN, QRG, FLAGS, WX)
|
||||
lazy val COL_DIST = "((lat - %d)*(lat - %d) + (lon - %d)*(lon - %d)*%d/100) as dist"
|
||||
|
||||
val COLUMN_TS = 1
|
||||
val COLUMN_CALL = 2
|
||||
val COLUMN_LAT = 3
|
||||
val COLUMN_LON = 4
|
||||
val COLUMN_SYMBOL = 5
|
||||
val COLUMN_COMMENT = 6
|
||||
val COLUMN_SPEED = 7
|
||||
val COLUMN_COURSE = 8
|
||||
val COLUMN_ALT = 9
|
||||
val COLUMN_SPEED = 5
|
||||
val COLUMN_COURSE = 6
|
||||
val COLUMN_ALT = 7
|
||||
val COLUMN_SYMBOL = 8
|
||||
val COLUMN_COMMENT = 9
|
||||
val COLUMN_ORIGIN = 10
|
||||
val COLUMN_QRG = 11
|
||||
val COLUMN_FLAGS = 12
|
||||
val COLUMN_WX = 13
|
||||
|
||||
lazy val COLUMNS_MAP = Array(_ID, CALL, LAT, LON, SYMBOL, ORIGIN)
|
||||
val COLUMN_MAP_CALL = 1
|
||||
|
@ -199,6 +201,9 @@ class StorageDatabase(context : Context) extends
|
|||
db.execSQL(Station.TABLE_CREATE)
|
||||
db.execSQL(Position.TABLE_CREATE)
|
||||
}
|
||||
if(from <= 3 && to <= 4) {
|
||||
db.execSQL("ALTER TABLE stations ADD COLUMN wx TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
def trimPosts(ts : Long) = Benchmark("trimPosts") {
|
||||
|
@ -225,6 +230,8 @@ class StorageDatabase(context : Context) extends
|
|||
val sym = "%s%s".format(pos.getSymbolTable(), pos.getSymbolCode())
|
||||
val comment = ap.getAprsInformation().getComment()
|
||||
val qrg = AprsPacket.parseQrg(comment)
|
||||
val wx = if(pos.getSymbolCode() == '_') AprsPacket.parseWx(comment) else if (pos.getSymbolCode() == '$') AprsPacket.parseWxPeet(comment) else ""
|
||||
|
||||
cv.put(TS, ts.asInstanceOf[java.lang.Long])
|
||||
cv.put(CALL, if (objectname != null) objectname else call)
|
||||
cv.put(LAT, lat.asInstanceOf[java.lang.Integer])
|
||||
|
@ -237,13 +244,15 @@ class StorageDatabase(context : Context) extends
|
|||
cv.put(SYMBOL, sym)
|
||||
cv.put(COMMENT, comment)
|
||||
cv.put(QRG, qrg)
|
||||
cv.put(WX, wx)
|
||||
|
||||
if (cse != null) {
|
||||
cv.put(SPEED, cse.getSpeed().asInstanceOf[java.lang.Integer])
|
||||
cv.put(COURSE, cse.getCourse().asInstanceOf[java.lang.Integer])
|
||||
}
|
||||
Log.d(TAG, "got %s(%d, %d)%s -> %s".format(call, lat, lon, sym, comment))
|
||||
Log.d(TAG, "got %s(%d, %d)%s -> %s %s".format(call, lat, lon, sym, comment, wx))
|
||||
// replace the full station info in stations table
|
||||
getWritableDatabase().replaceOrThrow(TABLE, CALL, cv)
|
||||
getWritableDatabase().replaceOrThrow(Station.TABLE, CALL, cv)
|
||||
}
|
||||
|
||||
def isMessageDuplicate(call : String, msgid : String, text : String) : Boolean = {
|
||||
|
|
Ładowanie…
Reference in New Issue