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 format
pull/159/head
Mathew McKernan 2017-03-16 10:16:29 +11:00
rodzic e17b20bdc3
commit 47d72f7d48
4 zmienionych plików z 135 dodań i 19 usunięć

Wyświetl plik

@ -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"

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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 = {