+++ /dev/null
-package tim.prune.function.weather;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-
-import javax.swing.BorderFactory;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.SwingUtilities;
-import javax.swing.table.TableCellRenderer;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import tim.prune.App;
-import tim.prune.GenericFunction;
-import tim.prune.GpsPrune;
-import tim.prune.I18nManager;
-import tim.prune.data.DataPoint;
-import tim.prune.data.NumberUtils;
-import tim.prune.data.Track;
-import tim.prune.function.browser.BrowserLauncher;
-
-/**
- * Function to display a weather forecast for the current location
- * using the services of openweathermap.org
- */
-public class GetWeatherForecastFunction extends GenericFunction implements Runnable
-{
- /** Dialog object */
- private JDialog _dialog = null;
- /** Label for location */
- private JLabel _locationLabel = null;
- /** Label for the forecast update time */
- private JLabel _updateTimeLabel = null;
- /** Label for the sunrise and sunset times */
- private JLabel _sunriseLabel = null;
- /** Radio button for selecting current weather */
- private JRadioButton _currentForecastRadio = null;
- /** Radio button for selecting daily forecasts */
- private JRadioButton _dailyForecastRadio = null;
- /** Dropdown for selecting celsius / fahrenheit */
- private JComboBox<String> _tempUnitsDropdown = null;
- /** Table to hold the forecasts */
- private JTable _forecastsTable = null;
- /** Table model */
- private WeatherTableModel _tableModel = new WeatherTableModel();
- /** Set of previously obtained results, to avoid repeating calls */
- private ResultSet _resultSet = new ResultSet();
- /** Location id obtained from current forecast */
- private String _locationId = null;
- /** Flag to show that forecast is currently running, don't start another */
- private boolean _isRunning = false;
-
- /** True to just simulate the calls and read files instead, false to call real API */
- private static final boolean SIMULATE_WITH_FILES = false;
- /** Unique API key for GpsPrune */
- private static final String OPENWEATHERMAP_API_KEY = "d1c5d792362f5a5c2eacf70a3b72ecd6";
-
-
- /**
- * Inner class to pass results asynchronously to the table model
- */
- private class ResultUpdater implements Runnable
- {
- private WeatherResults _results;
- public ResultUpdater(WeatherResults inResults) {
- _results = inResults;
- }
- public void run() {
- _tableModel.setResults(_results);
- adjustTable();
- }
- }
-
-
- /** Constructor */
- public GetWeatherForecastFunction(App inApp)
- {
- super(inApp);
- }
-
- /** @return name key */
- public String getNameKey() {
- return "function.getweatherforecast";
- }
-
- /**
- * Begin the function
- */
- public void begin()
- {
- // Initialise dialog, show empty list
- if (_dialog == null)
- {
- _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
- _dialog.setLocationRelativeTo(_parentFrame);
- _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- _dialog.getContentPane().add(makeDialogComponents());
- _dialog.pack();
- }
- // Clear results
- _locationId = null;
- _tableModel.clear();
- _locationLabel.setText(I18nManager.getText("confirm.running"));
- _updateTimeLabel.setText("");
- _sunriseLabel.setText("");
- _currentForecastRadio.setSelected(true);
-
- // Start new thread to load list asynchronously
- new Thread(this).start();
-
- _dialog.setVisible(true);
- }
-
- /**
- * Create dialog components
- * @return Panel containing all gui elements in dialog
- */
- private Component makeDialogComponents()
- {
- JPanel dialogPanel = new JPanel();
- dialogPanel.setLayout(new BorderLayout(0, 4));
-
- JPanel topPanel = new JPanel();
- topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
- _locationLabel = new JLabel(I18nManager.getText("confirm.running"));
- _locationLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
- topPanel.add(_locationLabel);
- _updateTimeLabel = new JLabel(" ");
- _updateTimeLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
- topPanel.add(_updateTimeLabel);
- _sunriseLabel = new JLabel(" ");
- _sunriseLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
- topPanel.add(_sunriseLabel);
- JPanel radioPanel = new JPanel();
- radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.X_AXIS));
- radioPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
- ButtonGroup forecastTypeGroup = new ButtonGroup();
- _currentForecastRadio = new JRadioButton(I18nManager.getText("dialog.weather.currentforecast"));
- _dailyForecastRadio = new JRadioButton(I18nManager.getText("dialog.weather.dailyforecast"));
- JRadioButton threeHourlyRadio = new JRadioButton(I18nManager.getText("dialog.weather.3hourlyforecast"));
- forecastTypeGroup.add(_currentForecastRadio);
- forecastTypeGroup.add(_dailyForecastRadio);
- forecastTypeGroup.add(threeHourlyRadio);
- radioPanel.add(_currentForecastRadio);
- radioPanel.add(_dailyForecastRadio);
- radioPanel.add(threeHourlyRadio);
- _currentForecastRadio.setSelected(true);
- ActionListener radioListener = new ActionListener() {
- public void actionPerformed(ActionEvent arg0) {
- if (!_isRunning) new Thread(GetWeatherForecastFunction.this).start();
- }
- };
- _currentForecastRadio.addActionListener(radioListener);
- _dailyForecastRadio.addActionListener(radioListener);
- threeHourlyRadio.addActionListener(radioListener);
- radioPanel.add(Box.createHorizontalGlue());
- radioPanel.add(Box.createHorizontalStrut(40));
-
- // Dropdown for temperature units
- radioPanel.add(new JLabel(I18nManager.getText("dialog.weather.temperatureunits") + ": "));
- _tempUnitsDropdown = new JComboBox<String>(new String[] {
- I18nManager.getText("units.degreescelsius"), I18nManager.getText("units.degreesfahrenheit")
- });
- _tempUnitsDropdown.setMaximumSize(_tempUnitsDropdown.getPreferredSize());
- _tempUnitsDropdown.addActionListener(radioListener);
- radioPanel.add(_tempUnitsDropdown);
- radioPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
- topPanel.add(radioPanel);
- dialogPanel.add(topPanel, BorderLayout.NORTH);
-
- final IconRenderer iconRenderer = new IconRenderer();
- _forecastsTable = new JTable(_tableModel)
- {
- public TableCellRenderer getCellRenderer(int row, int column) {
- if ((row == WeatherTableModel.ROW_ICON)) {
- return iconRenderer;
- }
- return super.getCellRenderer(row, column);
- }
- };
- _forecastsTable.setRowSelectionAllowed(false);
- _forecastsTable.setRowHeight(2, 55); // make just that row high enough to see icons
- _forecastsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
- _forecastsTable.getTableHeader().setReorderingAllowed(false);
- _forecastsTable.setShowHorizontalLines(false);
-
- JScrollPane scroller = new JScrollPane(_forecastsTable);
- scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
- scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
- scroller.setPreferredSize(new Dimension(500, 210));
- scroller.getViewport().setBackground(Color.white);
-
- dialogPanel.add(scroller, BorderLayout.CENTER);
-
- // button panel at bottom
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
- JButton launchButton = new JButton(I18nManager.getText("button.showwebpage"));
- launchButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent arg0) {
- BrowserLauncher.launchBrowser("http://openweathermap.org/city/" + (_locationId == null ? "" : _locationId));
- }
- });
- buttonPanel.add(launchButton);
- // close
- JButton closeButton = new JButton(I18nManager.getText("button.close"));
- closeButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- _dialog.dispose();
- }
- });
- buttonPanel.add(closeButton);
- // Add a holder panel with a static label to credit openweathermap
- JPanel southPanel = new JPanel();
- southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.Y_AXIS));
- southPanel.add(new JLabel(I18nManager.getText("dialog.weather.creditnotice")));
- southPanel.add(buttonPanel);
- dialogPanel.add(southPanel, BorderLayout.SOUTH);
- dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
- return dialogPanel;
- }
-
- /**
- * Get the weather forecast in a separate thread
- */
- public void run()
- {
- if (_isRunning) {return;} // don't run twice
- _isRunning = true;
-
- // Are we getting the current details, or getting a forecast?
- final boolean isCurrent = _locationId == null || _currentForecastRadio.isSelected();
- final boolean isDailyForecast = _dailyForecastRadio.isSelected() && !isCurrent;
- final boolean isHourlyForecast = !isCurrent && !isDailyForecast;
- final boolean isUsingCelsius = _tempUnitsDropdown.getSelectedIndex() == 0;
-
- // Have we got these results already? Look in store
- WeatherResults results = _resultSet.getWeather(_locationId, isCurrent, isDailyForecast, isHourlyForecast, isUsingCelsius);
- if (results == null)
- {
- if (isCurrent)
- {
- // Get the current details using either lat/long or locationId
- results = getCurrentWeather(isUsingCelsius);
- // If the current radio isn't selected, select it
- if (!_currentForecastRadio.isSelected()) {
- _currentForecastRadio.setSelected(true);
- }
- }
- else
- {
- // Get the specified forecast using the retrieved locationId
- results = getWeatherForecast(isDailyForecast, isUsingCelsius);
- }
- // If it's a valid answer, store it for later
- if (results != null)
- {
- _resultSet.setWeather(results, _locationId, isCurrent, isDailyForecast, isHourlyForecast, isUsingCelsius);
- }
- }
-
- // update table contents and labels
- if (results != null)
- {
- SwingUtilities.invokeLater(new ResultUpdater(results));
- _locationLabel.setText(I18nManager.getText("dialog.weather.location") + ": " + results.getLocationName());
- final String ut = results.getUpdateTime();
- _updateTimeLabel.setText(I18nManager.getText("dialog.weather.update") + ": " + (ut == null ? "" : ut));
- if (results.getSunriseTime() != null && results.getSunsetTime() != null)
- {
- _sunriseLabel.setText(I18nManager.getText("dialog.weather.sunrise") + ": " + results.getSunriseTime()
- + ", " + I18nManager.getText("dialog.weather.sunset") + ": " + results.getSunsetTime());
- }
- else {
- _sunriseLabel.setText("");
- }
- }
-
- // finished running
- _isRunning = false;
- }
-
-
- /**
- * Adjust the column widths and row heights to fit the displayed data
- */
- private void adjustTable()
- {
- if (!_tableModel.isEmpty())
- {
- // adjust column widths for all columns
- for (int i=0; i<_forecastsTable.getColumnCount(); i++)
- {
- double maxWidth = 0.0;
- for (int j=0; j<_forecastsTable.getRowCount(); j++)
- {
- final String value = _tableModel.getValueAt(j, i).toString();
- maxWidth = Math.max(maxWidth, _forecastsTable.getCellRenderer(0, 0).getTableCellRendererComponent(
- _forecastsTable, value, false, false, 0, 0).getPreferredSize().getWidth());
- }
- _forecastsTable.getColumnModel().getColumn(i).setMinWidth((int) maxWidth + 2);
- }
- // Set minimum row heights
- final int labelHeight = (int) (_forecastsTable.getCellRenderer(0, 0).getTableCellRendererComponent(
- _forecastsTable, "M", false, false, 0, 0).getMinimumSize().getHeight() * 1.2f + 4);
- for (int i=0; i<_forecastsTable.getRowCount(); i++)
- {
- if (i == WeatherTableModel.ROW_ICON) {
- _forecastsTable.setRowHeight(i, 55);
- }
- else {
- _forecastsTable.setRowHeight(i, labelHeight);
- }
- }
- }
- }
-
- /**
- * Get the current weather using the lat/long and populate _results
- * @param inUseCelsius true for celsius, false for fahrenheit
- * @return weather results
- */
- private WeatherResults getCurrentWeather(boolean inUseCelsius)
- {
- final Track track = _app.getTrackInfo().getTrack();
- if (track.getNumPoints() < 1) {return null;}
- // Get coordinates to lookup
- double lat = 0.0, lon = 0.0;
- // See if a point is selected, if so use that
- DataPoint currPoint = _app.getTrackInfo().getCurrentPoint();
- if (currPoint != null)
- {
- // Use selected point
- lat = currPoint.getLatitude().getDouble();
- lon = currPoint.getLongitude().getDouble();
- }
- else
- {
- lat = track.getLatRange().getMidValue();
- lon = track.getLonRange().getMidValue();
- }
-
- InputStream inStream = null;
- // Build url either with coordinates or with location id if available
- final String urlString = "http://api.openweathermap.org/data/2.5/weather?"
- + (_locationId == null ? ("lat=" + NumberUtils.formatNumberUk(lat, 5) + "&lon=" + NumberUtils.formatNumberUk(lon, 5))
- : ("id=" + _locationId))
- + "&lang=" + I18nManager.getText("openweathermap.lang")
- + "&mode=xml&units=" + (inUseCelsius ? "metric" : "imperial")
- + "&APPID=" + OPENWEATHERMAP_API_KEY;
- // System.out.println(urlString);
-
- // Parse the returned XML with a special handler
- OWMCurrentHandler xmlHandler = new OWMCurrentHandler();
- try
- {
- URL url = new URL(urlString);
- SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
- // DEBUG: Simulate the call in case of no network connection
- if (SIMULATE_WITH_FILES)
- {
- inStream = new FileInputStream(new File("tim/prune/test/examplecurrentweather.xml"));
- try {
- Thread.sleep(2000);
- } catch (InterruptedException tie) {}
- }
- else
- {
- URLConnection conn = url.openConnection();
- conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
- inStream = conn.getInputStream();
- }
-
- saxParser.parse(inStream, xmlHandler);
- }
- catch (Exception e)
- {
- // Show error message but don't close dialog
- _app.showErrorMessageNoLookup(getNameKey(), e.getClass().getName() + " - " + e.getMessage());
- _isRunning = false;
- return null;
- }
- // Close stream and ignore errors
- try {
- inStream.close();
- } catch (Exception e) {}
-
- // Save the location id
- if (xmlHandler.getLocationId() != null) {
- _locationId = xmlHandler.getLocationId();
- }
- // Get the results from the handler and return
- WeatherResults results = new WeatherResults();
- results.setForecast(xmlHandler.getCurrentWeather());
- results.setLocationName(xmlHandler.getLocationName());
- results.setUpdateTime(xmlHandler.getUpdateTime());
- results.setSunriseSunsetTimes(xmlHandler.getSunriseTime(), xmlHandler.getSunsetTime());
- results.setTempsCelsius(inUseCelsius);
- return results;
- }
-
-
- /**
- * Get the weather forecast for the current location id and populate in _results
- * @param inDaily true for daily, false for 3-hourly
- * @param inCelsius true for celsius, false for fahrenheit
- * @return weather results
- */
- private WeatherResults getWeatherForecast(boolean inDaily, boolean inCelsius)
- {
- InputStream inStream = null;
- // Build URL
- final String forecastCount = inDaily ? "8" : "3";
- final String urlString = "http://api.openweathermap.org/data/2.5/forecast"
- + (inDaily ? "/daily" : "") + "?id=" + _locationId + "&lang=" + I18nManager.getText("openweathermap.lang")
- + "&mode=xml&units=" + (inCelsius ? "metric" : "imperial") + "&cnt=" + forecastCount
- + "&APPID=" + OPENWEATHERMAP_API_KEY;
- // System.out.println(urlString);
-
- // Parse the returned XML with a special handler
- OWMForecastHandler xmlHandler = new OWMForecastHandler();
- try
- {
- URL url = new URL(urlString);
- SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
- // DEBUG: Simulate the call in case of no network connection
- if (SIMULATE_WITH_FILES)
- {
- inStream = new FileInputStream(new File("tim/prune/test/exampleweatherforecast.xml"));
- try {
- Thread.sleep(2000);
- } catch (InterruptedException tie) {}
- }
- else
- {
- URLConnection conn = url.openConnection();
- conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
- inStream = conn.getInputStream();
- }
-
- saxParser.parse(inStream, xmlHandler);
- }
- catch (Exception e)
- {
- // Show error message but don't close dialog
- _app.showErrorMessageNoLookup(getNameKey(), e.getClass().getName() + " - " + e.getMessage());
- _isRunning = false;
- return null;
- }
- // Close stream and ignore errors
- try {
- inStream.close();
- } catch (Exception e) {}
-
- // Get results from handler, put in model
- WeatherResults results = new WeatherResults();
- results.setForecasts(xmlHandler.getForecasts());
- results.setLocationName(xmlHandler.getLocationName());
- results.setUpdateTime(xmlHandler.getUpdateTime());
- results.setTempsCelsius(inCelsius);
- return results;
- }
-}