Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions app/src/main/assets/help.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ <h3>Avare Releases</h3>
<li>ADS-B improvements</li>
<li>ICAO format for entering GPS coordinates (see help)</li>
<li>Removed Travel screen</li>
<li>Added METAR/Performance based on METAR of a nearby field</li>
<li>Added Performance for fields without runway heading</li>
</ul>
<b>7.6.8</b><br>
<ul>
Expand Down Expand Up @@ -200,19 +202,37 @@ <h3 style="text-align: center;"><a name="0.1_Quick">Quick Start</a> -
<p style="padding-left: 30px;">To temporarily hide your notes/drawings and return to
normal Map functioning (Pan mode), just tap the Draw (Pan) button again.</p>

<p>•<b>Long-press</b> any point on the chart (with GPS
acquired) to display text at the top of the screen giving
approximate compass direction and distance<b>From</b>,
followed by exact GPS bearing<b>To</b>, that point in relation to
your GPS location. Very handy when contacting ATC or making CTAF
calls!</p>

<p style="padding-left: 30px;"><b>Note:</b> The current <b>Destination</b> used
by Avare for various features can be set after a long-press on the chart. Just tap either
the button that pops out containing the name or coordinates of the location you pressed;
or the button that pops out containing the word "Plan" to send that Destination to the flight plan.
<p>•<b>Long-press</b> any point on the chart to display a page containing:
<li style="padding-left: 30px;"><b>Action</b> buttons row:
<b>->D</b> sets up the point as Destination,
<b>+Plan</b> adds the point to the Plan,
<b>Plate</b> displays airport's plates,
<b>A/FD</b> displays a synthetic Chart Supplement page (the top dropdown opens scanned Chart Supplement),
<b>X</b> to close the page.</li>
<li style="padding-left: 30px;"><b>Airport FAA code</b> or <b>GPS coordinates</b> of the point
in <i>[name@]lat&lon</i> format; <i>name</i> is optional,</li>
<li style="padding-left: 30px;"><b>Position</b>: distance, compass direction and GPS bearing <b><u>FROM</u></b> the point,
followed by GPS bearing from your position <b><u>TO</u></b> the point, as well as <b>NavAids</b> (VOR) locations in format (VORName)(BearingFrom)(Distance).
Useful when contacting ATC or making CTAF calls.</li>

<br>If the point is an airport, weather is downloaded/not expired:
<li style="padding-left: 30px;">the airport's <b>METAR</b>
or the closest METAR found in a airport's radius (configurable in Preferences/Weather),</li>
<li style="padding-left: 30px;"><b>Performance</b> data for use at the airport:
density altitude, the best runway and its head/cross winds calculated using the METAR winds,</li>
<li style="padding-left: 30px;">the airport's <b>TAF</b>,
<li style="padding-left: 30px;"><b>Winds/Temp. Aloft</b> forecast reported by the closest meteorological station,</li>

<br>For all points:
<li style="padding-left: 30px;">nearby <b>Pireps</b></li>
<li style="padding-left: 30px;">nearby <b>Special Use Airspace</b> (SUA),</li>
<li style="padding-left: 30px;"><b>data source</b> for the weather and SUA:
data downloaded from internet or data retrieved with ADSB,</li>
<li style="padding-left: 30px;"><b>data timestamp</b>.</li>
</p>

<a name="0.1_Find"></a>
</p><p>•The "<b>Find</b>" button in the bottom Menu
<p>•The "<b>Find</b>" button in the bottom Menu
activates a search box for a <span style="text-decoration: underline;">4-digit</span>
Destination Airport code (or Navaid code, Fix code, etc.)
that Avare uses to display a course, bearing, etc. This
Expand Down
80 changes: 79 additions & 1 deletion app/src/main/java/com/ds/avare/storage/DataBaseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2061,7 +2061,85 @@ public Metar getMETAR(String station) {
return metar;
}


/**
* Return metar closest to the input coordinates using query on weather.metars table
* We query in a bounded 100mi wide/tall lat/lon box then sort based on distance from the input
* see also http://stackoverflow.com/questions/3695224/sqlite-getting-nearest-locations-with-latitude-and-longitude#
* @param lat of the central point
* @param lon of the central point
* @return
*/
public Metar getClosestMETAR(Double lat, Double lon) {

Metar metar = null;
final int searchRadius = mPref.getClosestMetarSearchRadius();
SquareBoxSearchHelper squareBoxSearchHelper = new SquareBoxSearchHelper(lat, lon, searchRadius);

String qry =
"select * from metars where 1=1 "
+ squareBoxSearchHelper.getWhereClause("latitude", "longitude")
+";";

Cursor cursor = doQueryWeather(qry, getWeatherDb());

try {
if(cursor != null) {
if(cursor.moveToFirst()) {

metar = new Metar();
metar.rawText = cursor.getString(0);
metar.time = cursor.getString(1);
metar.stationId = cursor.getString(2);
metar.flightCategory = cursor.getString(3);
if (!cursor.isNull(4) && !cursor.isNull(5)) {
metar.distance = Projection.getStaticDistance(lon, lat, cursor.getDouble(4), cursor.getDouble(5));

GeomagneticField gmf = new GeomagneticField(lat.floatValue(), lon.floatValue(), 0, System.currentTimeMillis());
Projection p = new Projection(cursor.getDouble(4), cursor.getDouble(5), lon, lat);
metar.position = Math.round(p.getDistance()) + Preferences.distanceConversionUnit + " " +
p.getGeneralDirectionFrom(-gmf.getDeclination());
}
}
}
}
catch (Exception e) {
}

closesWeather(cursor);
return metar;
}

private class SquareBoxSearchHelper {
private double lat, lon;
private Coordinate top;
private Coordinate bottom;
private Coordinate left;
private Coordinate right;
private double fudge;

public SquareBoxSearchHelper(double lat, double lon, double search_radius) {
this.lat = lat; this.lon = lon;
top = Projection.findStaticPoint(lon, lat, 0, search_radius);
bottom = Projection.findStaticPoint(lon, lat, 180, search_radius);
left = Projection.findStaticPoint(lon, lat, 270, search_radius);
right = Projection.findStaticPoint(lon, lat, 90, search_radius);
fudge = Math.pow(Math.cos(Math.toRadians(lat)), 2);
}

public String getWhereClause(String latField, String lonField) {
return " and "+latField+" < "+top.getLatitude()+
" and "+latField+" > "+bottom.getLatitude()+
" and "+lonField+" < "+right.getLongitude()+
" and "+lonField+" > "+left.getLongitude()+
" order by ((" +lat+" - "+latField+") * ("+lat+" - "+latField
+") + ("+lon+" - "+lonField+") * ("+lon+" - "+lonField
+") * "+fudge+")";
}

}



/**
*
* @param lon
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/java/com/ds/avare/storage/DataSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,17 @@ public Metar getMETAR(String station) {
}

/**
*
*
* @param lat
* @param lon
* @return
*/
public Metar getClosestMETAR(Double lat, Double lon) {
return dbHelper.getClosestMETAR(lat, lon);
}

/**
*
* @return
*/
public LinkedList<Airep> getAireps(double lon, double lat) {
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/com/ds/avare/storage/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,18 @@ public boolean isVerticalPfd() {
return mPref.getBoolean(mContext.getString(R.string.VerticalPfd), false);
}

public int getClosestMetarSearchRadius() {
try {
return (Integer.parseInt(mPref.getString(mContext.getString(R.string.NearestMETARRadius), "0")));
} catch (Exception x) {
return 0;
}
}

public void setClosestMetarSearchRadius(int set) {
mPref.edit().putString(mContext.getString(R.string.NearestMETARRadius), Integer.toString(set)).commit();
}

public int getWindsAloftCeiling() {
try {
return Integer.parseInt(mPref.getString(mContext.getString(R.string.WindsAloftCeiling), "39"));
Expand Down
101 changes: 63 additions & 38 deletions app/src/main/java/com/ds/avare/utils/WeatherHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import android.util.Pair;

import com.ds.avare.weather.Metar;

import java.util.LinkedList;
import java.util.Locale;

Expand Down Expand Up @@ -75,7 +77,8 @@ public static String formatWeather(String weather) {
weather = weather.replace("AMD ", "");
weather = weather.replace("\n\n", "\n");
weather = weather.replace(" FM", "\nFM");
weather = weather.replace("BECMG", "\nBECMG");
weather = weather.replace("TEMPO", "\nTEMPO");
weather = weather.replace("BECMG", "\nBECMG");
return weather;
}

Expand All @@ -90,10 +93,12 @@ public static String formatWeatherHTML(String weather, boolean translate) {
weather = weather.replace("\n", "<br>");
if(translate) {
weather = weather.replace(" FM", "</br>FM(From)<br>");
weather = weather.replace("TEMPO", "</br>TEMPO(Temporarily)<br>");
weather = weather.replace("BECMG", "</br>BECMG(Becoming)<br>");
}
else {
weather = weather.replace(" FM", "</br>FM");
weather = weather.replace("TEMPO", "</br>TEMPO");
weather = weather.replace("BECMG", "</br>BECMG");
}
return weather;
Expand Down Expand Up @@ -174,21 +179,42 @@ public static String formatTafHTML(String weatherAll, boolean translate) {
weather = weather.replaceAll("VV", "<font color='#ff2a00'>VV" + (translate ? "(Vertical Visibility)" : "") + "<font color='white'>");
weather = weather.replaceAll("CB", "<font color='#ff2a00'>CB" + (translate ? "(Cumulonimbus)" : "") + "<font color='white'>");
weather = weather.replaceAll("WS", "<font color='#ff54f9'>WS" + (translate ? "(Wind Shear)" : "") + "<font color='white'>");

weather = weather.replaceAll(" 9999 ", " 9999" + (translate ? "(Visibility > 7SM) " : ""));
weather = weather.replaceAll("QNH", "QNH" + (translate ? "(Minimum Altimeter)" : ""));
weather = weather.replaceAll("INS", "INS" + (translate ? "(Inches)" : ""));

for(int i = 1 ; i < strip.length; i++) {

String weather1 = strip[i];

weather += " RMK" + (translate ? "(Remark) " : " ") + weather1;
}

return weather;
}

public enum DistantMetarFormat { NoStationId, WithStationId }

/**
* @param metar
* @param format
* @param airport
* @return the metar decorated with the metar's position off airport name, if that metar is
* from a different field; for example for 3GV we'd print:
* METAR (8nm SW of 3GV)
*/
public static String formatDistantMetarHeader(Metar metar, DistantMetarFormat format, String airport) {
String stationId = format == DistantMetarFormat.WithStationId ?
"using " + metar.stationId + " "
: "";
return metar.distance > 0 ?
String.format(Locale.getDefault(),
"<font color=\"red\">(%s%s %s)</font> ",
stationId, metar.position, airport)
: "";
}

/**
* Split string on the second space
* @param s
Expand Down Expand Up @@ -221,6 +247,7 @@ public static String formatMetarHTML(String weatherAll, boolean translate) {
*/
identAndTime = identAndTime.replaceAll("SPECI", "SPECI" + (translate ? "(Special/unscheduled)" : ""));


/*
* Remarks
*/
Expand Down Expand Up @@ -322,6 +349,7 @@ public static String formatMetarHTML(String weatherAll, boolean translate) {
return identAndTime + " " + weather;
}


/**
* Color code winds
* @param weather
Expand Down Expand Up @@ -740,8 +768,8 @@ public static String getBestRunway(String metar, LinkedList<String> runways) {

String wind = "";
double dir = 0;
double spd0 = 0;
double spd1 = 0;
double windspeed = 0;
double gustspeed = 0;

boolean windset = false;

Expand All @@ -763,11 +791,11 @@ public static String getBestRunway(String metar, LinkedList<String> runways) {
}
dir = Double.parseDouble(tmp);
// next 2 digits are speed
spd0 = Double.parseDouble(wind.substring(3, 5));
windspeed = Double.parseDouble(wind.substring(3, 5));
// could be gusting
if(wind.contains("G")) {
// gusting to
spd1 = Double.parseDouble(wind.substring(6, 8));
gustspeed = Double.parseDouble(wind.substring(6, 8));
}
windset = true;
continue;
Expand All @@ -777,53 +805,50 @@ public static String getBestRunway(String metar, LinkedList<String> runways) {
catch (Exception e) {
}

double head1 = 0;
double head0 = 0;
double cross1 = 0;
double cross0 = 0;
double headgusts = 0;
double headwind = 0;
double crossgusts = 0;
double crosswind = 0;

if(windset) {
/*
* Find best wind aligned runway
* Find best wind-aligned runway name
*/
double maxW = -1E10;
String best = "";
double maxWind = -1E10;
String best_description = "";
for(String s : runways) {
String run[] = s.split(",");
String runway_name_and_heading[] = s.split(",");
try {
double rhead = Double.parseDouble(run[1]);
double rwy_heading = runway_name_and_heading.length == 2
? Double.parseDouble(runway_name_and_heading[1]) // parse rwy heading
: Double.parseDouble(runway_name_and_heading[0])*10; // parse rwy name - case of private fields which have no heading info
// find cross and head wind components
// aviation formulary
head0 = spd0 * Math.cos(Math.toRadians(dir - rhead));
if(head0 > maxW) {
headwind = windspeed * Math.cos(Math.toRadians(dir - rwy_heading));
if(headwind > maxWind) {
// find runway with max headwind component
maxW = head0;
cross0 = spd0 * Math.sin(Math.toRadians(dir - rhead));
if(spd1 != 0) {
head1 = spd1 * Math.cos(Math.toRadians(dir - rhead));
cross1 = spd1 * Math.sin(Math.toRadians(dir - rhead));
}
best = run[0];
best += "\n " + Math.abs((int)head0);
if(spd1 != 0) {
best += "G" + Math.abs((int)head1);
}
// T = tail, H = head, L = left, R = right
best += (head0 < 0 ? "KT Tail" : "KT Head");
best += "\n " + Math.abs((int)cross0);
if(spd1 != 0) {
best += "G" + Math.abs((int)cross1);
maxWind = headwind;
crosswind = windspeed * Math.sin(Math.toRadians(dir - rwy_heading));
if(gustspeed != 0) {
headgusts = gustspeed * Math.cos(Math.toRadians(dir - rwy_heading));
crossgusts = gustspeed * Math.sin(Math.toRadians(dir - rwy_heading));
}

best += (cross0 < 0 ? "KT Left X" : "KT Right X");
best_description = runway_name_and_heading[0] // rwy name
+ "\n " + Math.abs((int)headwind)
+ (gustspeed != 0 ? "G" + Math.abs((int)headgusts) : "")
// T = tail, H = head, L = left, R = right
+ (headwind < 0 ? "KT Tail" : "KT Head")
+ "\n " + Math.abs((int)crosswind)
+ (gustspeed != 0 ? "G" + Math.abs((int)crossgusts) : "")
+ (crosswind < 0 ? "KT Left X" : "KT Right X");
}
}
catch (Exception e) {
continue;
}

}
return best;
return best_description;
}

return "";
Expand Down
Loading