com.nadmm.airports.wx.TafFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.nadmm.airports.wx.TafFragment.java

Source

/*
 * FlightIntel for Pilots
 *
 * Copyright 2012 Nadeem Hasan <nhasan@nadmm.com>
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

package com.nadmm.airports.wx;

import java.util.Locale;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.nadmm.airports.DatabaseManager;
import com.nadmm.airports.DatabaseManager.Airports;
import com.nadmm.airports.DatabaseManager.Awos1;
import com.nadmm.airports.DatabaseManager.Wxs;
import com.nadmm.airports.DrawerActivityBase;
import com.nadmm.airports.FragmentBase;
import com.nadmm.airports.R;
import com.nadmm.airports.utils.CursorAsyncTask;
import com.nadmm.airports.utils.FormatUtils;
import com.nadmm.airports.utils.GeoUtils;
import com.nadmm.airports.utils.TimeUtils;
import com.nadmm.airports.wx.Taf.Forecast;
import com.nadmm.airports.wx.Taf.IcingCondition;
import com.nadmm.airports.wx.Taf.TurbulenceCondition;

public class TafFragment extends FragmentBase {

    private final String mAction = NoaaService.ACTION_GET_TAF;
    private final int TAF_RADIUS = 25;
    private final int TAF_HOURS_BEFORE = 3;

    private Location mLocation;
    private IntentFilter mFilter;
    private BroadcastReceiver mReceiver;
    private String mStationId;
    private Forecast mLastForecast;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);

        mFilter = new IntentFilter();
        mFilter.addAction(mAction);

        mReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action.equals(mAction)) {
                    String type = intent.getStringExtra(NoaaService.TYPE);
                    if (type.equals(NoaaService.TYPE_TEXT)) {
                        showTaf(intent);
                    }
                }
            }
        };
    }

    @Override
    public void onResume() {
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(getActivity());
        bm.registerReceiver(mReceiver, mFilter);
        Bundle args = getArguments();
        String stationId = args.getString(NoaaService.STATION_ID);
        setBackgroundTask(new TafTask()).execute(stationId);

        super.onResume();
    }

    @Override
    public void onPause() {
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(getActivity());
        bm.unregisterReceiver(mReceiver);

        super.onPause();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.taf_detail_view, container, false);
        Button btnGraphic = (Button) view.findViewById(R.id.btnViewGraphic);
        btnGraphic.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), TafMapActivity.class);
                startActivity(intent);
            }
        });
        return createContentView(view);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        DrawerActivityBase activity = (DrawerActivityBase) getActivity();
        setRefreshItemVisible(!activity.isDrawerOpen());
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.menu_refresh:
            startRefreshAnimation();
            requestTaf(mStationId, true);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    private final class TafTask extends CursorAsyncTask {

        @Override
        protected Cursor[] doInBackground(String... params) {
            String stationId = params[0];

            Cursor[] cursors = new Cursor[2];
            SQLiteDatabase db = getDbManager().getDatabase(DatabaseManager.DB_FADDS);

            SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
            builder.setTables(Wxs.TABLE_NAME);
            String selection = Wxs.STATION_ID + "=?";
            Cursor c = builder.query(db, new String[] { "*" }, selection, new String[] { stationId }, null, null,
                    null, null);
            c.moveToFirst();
            String siteTypes = c.getString(c.getColumnIndex(Wxs.STATION_SITE_TYPES));
            if (!siteTypes.contains("TAF")) {
                // There is no TAF available at this station, search for the nearest
                double lat = c.getDouble(c.getColumnIndex(Wxs.STATION_LATITUDE_DEGREES));
                double lon = c.getDouble(c.getColumnIndex(Wxs.STATION_LONGITUDE_DEGREES));
                Location location = new Location("");
                location.setLatitude(lat);
                location.setLongitude(lon);
                c.close();

                // Get the bounding box first to do a quick query as a first cut
                double[] box = GeoUtils.getBoundingBoxRadians(location, TAF_RADIUS);
                double radLatMin = box[0];
                double radLatMax = box[1];
                double radLonMin = box[2];
                double radLonMax = box[3];
                // Check if 180th Meridian lies within the bounding Box
                boolean isCrossingMeridian180 = (radLonMin > radLonMax);

                selection = "(" + Wxs.STATION_LATITUDE_DEGREES + ">=? AND " + Wxs.STATION_LATITUDE_DEGREES + "<=?"
                        + ") AND (" + Wxs.STATION_LONGITUDE_DEGREES + ">=? "
                        + (isCrossingMeridian180 ? "OR " : "AND ") + Wxs.STATION_LONGITUDE_DEGREES + "<=?)";
                String[] selectionArgs = { String.valueOf(Math.toDegrees(radLatMin)),
                        String.valueOf(Math.toDegrees(radLatMax)), String.valueOf(Math.toDegrees(radLonMin)),
                        String.valueOf(Math.toDegrees(radLonMax)) };
                c = builder.query(db, new String[] { "*" }, selection, selectionArgs, null, null, null, null);
                stationId = "";
                if (c.moveToFirst()) {
                    float distance = Float.MAX_VALUE;
                    do {
                        siteTypes = c.getString(c.getColumnIndex(Wxs.STATION_SITE_TYPES));
                        if (!siteTypes.contains("TAF")) {
                            continue;
                        }
                        // Get the location of this station
                        float[] results = new float[2];
                        Location.distanceBetween(location.getLatitude(), location.getLongitude(),
                                c.getDouble(c.getColumnIndex(Wxs.STATION_LATITUDE_DEGREES)),
                                c.getDouble(c.getColumnIndex(Wxs.STATION_LONGITUDE_DEGREES)), results);
                        results[0] /= GeoUtils.METERS_PER_NAUTICAL_MILE;
                        if (results[0] <= TAF_RADIUS && results[0] < distance) {
                            stationId = c.getString(c.getColumnIndex(Wxs.STATION_ID));
                            distance = results[0];
                        }
                    } while (c.moveToNext());
                }
            }
            c.close();

            if (stationId.length() > 0) {
                // We have the station with TAF
                builder = new SQLiteQueryBuilder();
                builder.setTables(Wxs.TABLE_NAME);
                selection = Wxs.STATION_ID + "=?";
                c = builder.query(db, new String[] { "*" }, selection, new String[] { stationId }, null, null, null,
                        null);
                cursors[0] = c;

                String[] wxColumns = new String[] { Awos1.WX_SENSOR_IDENT, Awos1.WX_SENSOR_TYPE,
                        Awos1.STATION_FREQUENCY, Awos1.SECOND_STATION_FREQUENCY, Awos1.STATION_PHONE_NUMBER,
                        Airports.ASSOC_CITY, Airports.ASSOC_STATE };
                builder = new SQLiteQueryBuilder();
                builder.setTables(Airports.TABLE_NAME + " a" + " LEFT JOIN " + Awos1.TABLE_NAME + " w" + " ON a."
                        + Airports.FAA_CODE + " = w." + Awos1.WX_SENSOR_IDENT);
                selection = "a." + Airports.ICAO_CODE + "=?";
                c = builder.query(db, wxColumns, selection, new String[] { stationId }, null, null, null, null);
                cursors[1] = c;
            }

            return cursors;
        }

        @Override
        protected boolean onResult(Cursor[] result) {
            Cursor wxs = result[0];
            if (wxs == null || !wxs.moveToFirst()) {
                // No station with TAF was found nearby
                Bundle args = getArguments();
                String stationId = args.getString(NoaaService.STATION_ID);

                View detail = findViewById(R.id.wx_detail_layout);
                detail.setVisibility(View.GONE);
                LinearLayout layout = (LinearLayout) findViewById(R.id.wx_status_layout);
                layout.removeAllViews();
                layout.setVisibility(View.GONE);
                TextView tv = (TextView) findViewById(R.id.status_msg);
                tv.setVisibility(View.VISIBLE);
                tv.setText(String.format("No wx station with TAF was found near %s" + " within %dNM radius",
                        stationId, TAF_RADIUS));
                View title = findViewById(R.id.wx_title_layout);
                title.setVisibility(View.GONE);
                stopRefreshAnimation();
                setContentShown(true);
            } else {
                mLocation = new Location("");
                float lat = wxs.getFloat(wxs.getColumnIndex(Wxs.STATION_LATITUDE_DEGREES));
                float lon = wxs.getFloat(wxs.getColumnIndex(Wxs.STATION_LONGITUDE_DEGREES));
                mLocation.setLatitude(lat);
                mLocation.setLongitude(lon);

                // Show the weather station info
                showWxTitle(result);

                // Now request the weather
                mStationId = wxs.getString(wxs.getColumnIndex(Wxs.STATION_ID));
                requestTaf(mStationId, false);
            }
            return true;
        }

    }

    protected void requestTaf(String stationId, boolean refresh) {
        Intent service = new Intent(getActivity(), TafService.class);
        service.setAction(mAction);
        service.putExtra(NoaaService.TYPE, NoaaService.TYPE_TEXT);
        service.putExtra(NoaaService.STATION_ID, stationId);
        service.putExtra(NoaaService.HOURS_BEFORE, TAF_HOURS_BEFORE);
        service.putExtra(NoaaService.FORCE_REFRESH, refresh);
        getActivity().startService(service);
    }

    protected void showTaf(Intent intent) {
        if (getActivity() == null) {
            return;
        }

        Taf taf = (Taf) intent.getSerializableExtra(NoaaService.RESULT);
        if (taf == null) {
            return;
        }

        View detail = findViewById(R.id.wx_detail_layout);
        LinearLayout layout = (LinearLayout) findViewById(R.id.wx_status_layout);
        TextView tv = (TextView) findViewById(R.id.status_msg);
        layout.removeAllViews();
        if (!taf.isValid) {
            tv.setVisibility(View.VISIBLE);
            layout.setVisibility(View.VISIBLE);
            tv.setText("Unable to get TAF for this location.");
            addRow(layout, "This could be due to the following reasons:");
            addBulletedRow(layout, "Network connection is not available");
            addBulletedRow(layout, "ADDS does not publish TAF for this station");
            addBulletedRow(layout, "Station is currently out of service");
            addBulletedRow(layout, "Station has not updated the TAF for more than 12 hours");
            detail.setVisibility(View.GONE);
            stopRefreshAnimation();
            setFragmentContentShown(true);
            return;
        } else {
            tv.setText("");
            tv.setVisibility(View.GONE);
            layout.setVisibility(View.GONE);
            detail.setVisibility(View.VISIBLE);
        }

        tv = (TextView) findViewById(R.id.wx_age);
        tv.setText(TimeUtils.formatElapsedTime(taf.issueTime));

        // Raw Text
        tv = (TextView) findViewById(R.id.wx_raw_taf);
        tv.setText(taf.rawText.replaceAll("(FM|BECMG|TEMPO)", "\n    $1"));

        layout = (LinearLayout) findViewById(R.id.taf_summary_layout);
        layout.removeAllViews();
        String fcstType;
        if (taf.rawText.startsWith("TAF AMD ")) {
            fcstType = "Amendment";
        } else if (taf.rawText.startsWith("TAF COR ")) {
            fcstType = "Correction";
        } else {
            fcstType = "Normal";
        }
        addRow(layout, "Forecast type", fcstType);
        addRow(layout, "Issued at", TimeUtils.formatDateTime(getActivity(), taf.issueTime));
        addRow(layout, "Valid from", TimeUtils.formatDateTime(getActivity(), taf.validTimeFrom));
        addRow(layout, "Valid to", TimeUtils.formatDateTime(getActivity(), taf.validTimeTo));
        if (taf.remarks != null && taf.remarks.length() > 0 && !taf.remarks.equals("AMD")) {
            addRow(layout, "\u2022 " + taf.remarks);
        }

        LinearLayout topLayout = (LinearLayout) findViewById(R.id.taf_forecasts_layout);
        topLayout.removeAllViews();

        StringBuilder sb = new StringBuilder();
        for (Forecast forecast : taf.forecasts) {
            RelativeLayout grp_layout = (RelativeLayout) inflate(R.layout.grouped_detail_item);

            // Keep track of forecast conditions across all change groups
            if (mLastForecast == null || forecast.changeIndicator == null
                    || forecast.changeIndicator.equals("FM")) {
                mLastForecast = forecast;
            } else {
                if (forecast.visibilitySM < Float.MAX_VALUE) {
                    mLastForecast.visibilitySM = forecast.visibilitySM;
                }
                if (forecast.skyConditions.size() > 0) {
                    mLastForecast.skyConditions = forecast.skyConditions;
                }
            }

            sb.setLength(0);
            if (forecast.changeIndicator != null) {
                sb.append(forecast.changeIndicator);
                sb.append(" ");
            }
            sb.append(TimeUtils.formatDateRange(getActivity(), forecast.timeFrom, forecast.timeTo));
            tv = (TextView) grp_layout.findViewById(R.id.group_extra);
            tv.setVisibility(View.GONE);
            tv = (TextView) grp_layout.findViewById(R.id.group_name);
            tv.setText(sb.toString());
            String flightCategory = WxUtils.computeFlightCategory(mLastForecast.skyConditions,
                    mLastForecast.visibilitySM);
            WxUtils.setFlightCategoryDrawable(tv, flightCategory);

            LinearLayout fcst_layout = (LinearLayout) grp_layout.findViewById(R.id.group_details);

            if (forecast.probability < Integer.MAX_VALUE) {
                addRow(fcst_layout, "Probability", String.format("%d%%", forecast.probability));
            }

            if (forecast.changeIndicator != null && forecast.changeIndicator.equals("BECMG")) {
                addRow(fcst_layout, "Becoming at", TimeUtils.formatDateTime(getActivity(), forecast.timeBecoming));
            }

            if (forecast.windSpeedKnots < Integer.MAX_VALUE) {
                String wind;
                if (forecast.windDirDegrees == 0 && forecast.windSpeedKnots == 0) {
                    wind = "Calm";
                } else if (forecast.windDirDegrees == 0) {
                    wind = String.format(Locale.US, "Variable at %d knots", forecast.windSpeedKnots);
                } else {
                    wind = String.format(Locale.US, "%s (%s true) at %d knots",
                            GeoUtils.getCardinalDirection(forecast.windDirDegrees),
                            FormatUtils.formatDegrees(forecast.windDirDegrees), forecast.windSpeedKnots);
                }
                String gust = "";
                if (forecast.windGustKnots < Integer.MAX_VALUE) {
                    gust = String.format(Locale.US, "Gusting to %d knots", forecast.windGustKnots);
                }
                addRow(fcst_layout, "Winds", wind, gust);
            }

            if (forecast.visibilitySM < Float.MAX_VALUE) {
                String value = forecast.visibilitySM > 6 ? "6+ SM"
                        : FormatUtils.formatStatuteMiles(forecast.visibilitySM);
                addRow(fcst_layout, "Visibility", value);
            }

            if (forecast.vertVisibilityFeet < Integer.MAX_VALUE) {
                addRow(fcst_layout, "Visibility", FormatUtils.formatFeetAgl(forecast.vertVisibilityFeet));
            }

            for (WxSymbol wx : forecast.wxList) {
                addRow(fcst_layout, "Weather", wx.toString());
            }

            for (SkyCondition sky : forecast.skyConditions) {
                addRow(fcst_layout, "Clouds", sky.toString());
            }

            if (forecast.windShearSpeedKnots < Integer.MAX_VALUE) {
                String shear = String.format(Locale.US, "%s (%s true) at %d knots",
                        GeoUtils.getCardinalDirection(forecast.windShearDirDegrees),
                        FormatUtils.formatDegrees(forecast.windShearDirDegrees), forecast.windShearSpeedKnots);
                String height = FormatUtils.formatFeetAgl(forecast.windShearHeightFeetAGL);
                addRow(fcst_layout, "Wind shear", shear, height);
            }

            if (forecast.altimeterHg < Float.MAX_VALUE) {
                addRow(fcst_layout, "Altimeter", FormatUtils.formatAltimeter(forecast.altimeterHg));
            }

            for (TurbulenceCondition turbulence : forecast.turbulenceConditions) {
                String value = WxUtils.decodeTurbulenceIntensity(turbulence.intensity);
                String height = FormatUtils.formatFeetRangeAgl(turbulence.minAltitudeFeetAGL,
                        turbulence.maxAltitudeFeetAGL);
                addRow(fcst_layout, "Turbulence", value, height);
            }

            for (IcingCondition icing : forecast.icingConditions) {
                String value = WxUtils.decodeIcingIntensity(icing.intensity);
                String height = FormatUtils.formatFeetRangeAgl(icing.minAltitudeFeetAGL, icing.maxAltitudeFeetAGL);
                addRow(fcst_layout, "Icing", value, height);
            }

            topLayout.addView(grp_layout, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }

        tv = (TextView) findViewById(R.id.wx_fetch_time);
        tv.setText("Fetched on " + TimeUtils.formatDateTime(getActivity(), taf.fetchTime));
        tv.setVisibility(View.VISIBLE);

        stopRefreshAnimation();
        setFragmentContentShown(true);
    }

}