/** * GPSDemo.java * * Copyright � 1998-2011 Research In Motion Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Note: For the sake of simplicity, this sample application may not leverage * resource bundles and resource strings. However, it is STRONGLY recommended * that application developers make use of the localization features available * within the BlackBerry development platform to ensure a seamless application * experience across a variety of languages and geographies. For more information * on localizing your application, please refer to the BlackBerry Java Development * Environment Development Guide associated with this release. */ package com.rim.samples.device.gpsdemo; import java.util.Vector; import javax.microedition.location.Criteria; import javax.microedition.location.Location; import javax.microedition.location.LocationException; import javax.microedition.location.LocationListener; import javax.microedition.location.LocationProvider; import net.rim.device.api.command.Command; import net.rim.device.api.command.CommandHandler; import net.rim.device.api.command.ReadOnlyCommandMetadata; import net.rim.device.api.gps.GPSInfo; import net.rim.device.api.system.PersistentObject; import net.rim.device.api.system.PersistentStore; import net.rim.device.api.ui.Field; import net.rim.device.api.ui.MenuItem; import net.rim.device.api.ui.UiApplication; import net.rim.device.api.ui.component.Dialog; import net.rim.device.api.ui.component.EditField; import net.rim.device.api.ui.component.RichTextField; import net.rim.device.api.ui.component.TextField; import net.rim.device.api.ui.container.MainScreen; import net.rim.device.api.util.Persistable; import net.rim.device.api.util.StringProvider; /** * This application acts as a simple travel computer, recording route * coordinates, speed and altitude. Recording begins as soon as the application * is invoked. */ public class GPSDemo extends UiApplication { // Represents the number of updates over which altitude is calculated, in // seconds private static final int GRADE_INTERVAL = 5; // com.rim.samples.device.gpsdemo.GPSDemo.ID private static final long ID = 0x5d459971bb15ae7aL; // Represents period of the position query, in seconds private static int _interval = 1; private static Vector _previousPoints; private static float[] _altitudes; private static float[] _horizontalDistances; private static PersistentObject _store; // Initialize or reload the persisted WayPoints static { _store = PersistentStore.getPersistentObject(ID); if (_store.getContents() == null) { _previousPoints = new Vector(); _store.setContents(_previousPoints); } _previousPoints = (Vector) _store.getContents(); } private long _startTime; private float _wayHorizontalDistance; private float _horizontalDistance; private float _verticalDistance; private double _latitude; private double _longitude; private final EditField _status; private LocationProvider _locationProvider; private final GPSDemoScreen _screen; /** * Entry point for application * * @param args * Command line arguments (not used) */ public static void main(final String[] args) { // Create a new instance of the application and make the currently // running thread the application's event dispatch thread. new GPSDemo().enterEventDispatcher(); } /** * Create a new GPSDemo object */ public GPSDemo() { // Used by WayPoints, represents the time since the last waypoint _startTime = System.currentTimeMillis(); _altitudes = new float[GRADE_INTERVAL]; _horizontalDistances = new float[GRADE_INTERVAL]; _screen = new GPSDemoScreen(); _screen.setTitle("GPS Demo"); _status = new EditField(Field.NON_FOCUSABLE); _screen.add(_status); // Attempt to start the location listening thread if (startLocationUpdate()) { _screen.setState(_locationProvider.getState()); } // Render the screen pushScreen(_screen); } /** * Update the GUI with the data just received * * @param msg * The message to display */ private void updateLocationScreen(final String msg) { invokeLater(new Runnable() { public void run() { _status.setText(msg); } }); } /** * Invokes the Location API with Standalone criteria * * @return True if the <code>LocationProvider</code> was successfully * started, false otherwise */ private boolean startLocationUpdate() { boolean returnValue = false; if (!(GPSInfo.getDefaultGPSMode() == GPSInfo.GPS_MODE_NONE)) { try { final Criteria criteria = new Criteria(); criteria.setCostAllowed(false); _locationProvider = LocationProvider.getInstance(criteria); if (_locationProvider != null) { /* * Only a single listener can be associated with a provider, * and unsetting it involves the same call but with null. * Therefore, there is no need to cache the listener * instance request an update every second. */ _locationProvider.setLocationListener( new LocationListenerImpl(), _interval, -1, -1); returnValue = true; } else { invokeLater(new Runnable() { public void run() { Dialog.alert("Failed to obtain a location provider, exiting..."); System.exit(0); } }); } } catch (final LocationException le) { invokeLater(new Runnable() { public void run() { Dialog.alert("Failed to instantiate LocationProvider object, exiting..." + le.toString()); System.exit(0); } }); } } else { invokeLater(new Runnable() { public void run() { Dialog.alert("GPS is not supported on this device, exiting..."); System.exit(0); } }); } return returnValue; } /** * Marks a WayPoint in the persistent store. Calculations are based on all * data collected since the previous way point, or from the start of the * application if no previous WayPoints exist. */ private void markPoint() { final long current = System.currentTimeMillis(); final WayPoint point = new WayPoint(_latitude, _longitude, _startTime, current, _wayHorizontalDistance, _verticalDistance); addWayPoint(point); // Reset the WayPoint variables _startTime = current; _wayHorizontalDistance = 0; _verticalDistance = 0; } /** * View the various saved WayPoints */ private void viewPreviousPoints() { final PointScreen pointScreen = new PointScreen(_previousPoints); pushScreen(pointScreen); } /** * Adds a new WayPoint and commits the set of saved waypoints to persistent * store. * * @param point * The WayPoint to add */ private synchronized static void addWayPoint(final WayPoint point) { _previousPoints.addElement(point); commit(); } /** * Removes a WayPoint from the set of saved points and commits the modifed * set to persistent store * * @param point * The WayPoint to remove */ synchronized static void removeWayPoint(final WayPoint point) { _previousPoints.removeElement(point); commit(); } /** * Commits the WayPoint set to persistent store */ private static void commit() { _store.setContents(_previousPoints); _store.commit(); } /** * Implementation of the LocationListener interface. Listens for updates to * the device location and displays the results. */ private class LocationListenerImpl implements LocationListener { /** * @see javax.microedition.location.LocationListener#locationUpdated(LocationProvider,Location) */ public void locationUpdated(final LocationProvider provider, final Location location) { if (location.isValid()) { final float heading = location.getCourse(); _longitude = location.getQualifiedCoordinates().getLongitude(); _latitude = location.getQualifiedCoordinates().getLatitude(); final float altitude = location.getQualifiedCoordinates().getAltitude(); final float speed = location.getSpeed(); // Horizontal distance for current Location final float horizontalDistance = speed * _interval; _horizontalDistance += horizontalDistance; // Horizontal distance for WayPoint _wayHorizontalDistance += horizontalDistance; // Distance over the current interval float totalDist = 0; // Moving average grade for (int i = 0; i < GRADE_INTERVAL - 1; ++i) { _altitudes[i] = _altitudes[i + 1]; _horizontalDistances[i] = _horizontalDistances[i + 1]; totalDist = totalDist + _horizontalDistances[i]; } _altitudes[GRADE_INTERVAL - 1] = altitude; _horizontalDistances[GRADE_INTERVAL - 1] = speed * _interval; totalDist = totalDist + _horizontalDistances[GRADE_INTERVAL - 1]; final float grade = totalDist == 0.0F ? Float.NaN : (_altitudes[4] - _altitudes[0]) * 100 / totalDist; // Running total of the vertical distance gain final float altGain = _altitudes[GRADE_INTERVAL - 1] - _altitudes[GRADE_INTERVAL - 2]; if (altGain > 0) { _verticalDistance = _verticalDistance + altGain; } // Information to be displayed on the device final StringBuffer sb = new StringBuffer(); sb.append("Longitude: "); sb.append(_longitude); sb.append("\n"); sb.append("Latitude: "); sb.append(_latitude); sb.append("\n"); sb.append("Altitude: "); sb.append(altitude); sb.append(" m"); sb.append("\n"); sb.append("Heading relative to true north: "); sb.append(heading); sb.append("\n"); sb.append("Speed : "); sb.append(speed); sb.append(" m/s"); sb.append("\n"); sb.append("Grade : "); if (Float.isNaN(grade)) { sb.append(" Not available"); } else { sb.append(grade + " %"); } GPSDemo.this.updateLocationScreen(sb.toString()); } } /** * @see javax.microedition.location.LocationListener#providerStateChanged(LocationProvider, * int) */ public void providerStateChanged(final LocationProvider provider, final int newState) { if (newState == LocationProvider.TEMPORARILY_UNAVAILABLE) { provider.reset(); } _screen.setState(newState); } } /** * The main screen to display the current GPS information */ private final class GPSDemoScreen extends MainScreen { TextField _statusTextField; /** * Create a new GPSDemoScreen object */ GPSDemoScreen() { // Initialize UI _statusTextField = new TextField(Field.NON_FOCUSABLE); _statusTextField.setLabel("GPS Status: "); add(_statusTextField); final RichTextField instructions = new RichTextField("Waiting for location update...", Field.NON_FOCUSABLE); add(instructions); // Menu Item to add the current location to the list of WayPoints final MenuItem markWayPoint = new MenuItem(new StringProvider("Mark waypoint"), 0x230010, 0); markWayPoint.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { GPSDemo.this.markPoint(); } })); // Menu Item to view the marked WayPoints final MenuItem viewWayPoints = new MenuItem(new StringProvider("View waypoints"), 0x230020, 1); viewWayPoints.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { GPSDemo.this.viewPreviousPoints(); } })); addMenuItem(markWayPoint); addMenuItem(viewWayPoints); } /** * Display the state of the GPS service * * @param newState * The state to display */ public void setState(final int newState) { UiApplication.getUiApplication().invokeLater(new Runnable() { /** * @see java.lang.Runnable#run() */ public void run() { switch (newState) { case LocationProvider.AVAILABLE: _statusTextField.setText("Available"); break; case LocationProvider.OUT_OF_SERVICE: _statusTextField.setText("Out of Service"); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: _statusTextField.setText("Temporarily Unavailable"); break; } } }); } /** * @see net.rim.device.api.ui.Screen#close() */ public void close() { if (_locationProvider != null) { _locationProvider.reset(); _locationProvider.setLocationListener(null, -1, -1, -1); } super.close(); } } /** * A class to represent a way point, a marker on a journey or a point of * interest. */ static class WayPoint implements Persistable { long _startTime; long _endTime; double _latitude; double _longitude; float _distance; float _verticalDistance; /** * Creates a new WayPoint object * * @param latitude * Latitude of waypoint * @param longitude * Longitude of waypoint * @param startTime * Time at start of update * @param endTime * Time at end of update * @param distance * Distance travelled * @param verticalDistance * Vertical distance travelled */ WayPoint(final double latitude, final double longitude, final long startTime, final long endTime, final float distance, final float verticalDistance) { _startTime = startTime; _endTime = endTime; _distance = distance; _verticalDistance = verticalDistance; _latitude = latitude; _longitude = longitude; } } }