| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| package com.android.internal.location; |
| |
| import java.util.Calendar; |
| import java.util.GregorianCalendar; |
| import java.util.TimeZone; |
| |
| import android.location.Location; |
| import android.os.Bundle; |
| import android.util.Log; |
| |
| /** |
| * {@hide} |
| */ |
| public class NmeaParser { |
| |
| private static final String TAG = "NmeaParser"; |
| |
| private static final TimeZone sUtcTimeZone = TimeZone.getTimeZone("UTC"); |
| |
| private static final float KNOTS_TO_METERS_PER_SECOND = 0.51444444444f; |
| |
| private final String mName; |
| |
| private int mYear = -1; |
| private int mMonth; |
| private int mDay; |
| |
| private long mTime = -1; |
| private long mBaseTime; |
| private double mLatitude; |
| private double mLongitude; |
| |
| private boolean mHasAltitude; |
| private double mAltitude; |
| private boolean mHasBearing; |
| private float mBearing; |
| private boolean mHasSpeed; |
| private float mSpeed; |
| |
| private boolean mNewWaypoint = false; |
| private Location mLocation = null; |
| private Bundle mExtras; |
| |
| public NmeaParser(String name) { |
| mName = name; |
| } |
| |
| private boolean updateTime(String time) { |
| if (time.length() < 6) { |
| return false; |
| } |
| if (mYear == -1) { |
| // Since we haven't seen a day/month/year yet, |
| // we can't construct a meaningful time stamp. |
| // Clean up any old data. |
| mLatitude = 0.0; |
| mLongitude = 0.0; |
| mHasAltitude = false; |
| mHasBearing = false; |
| mHasSpeed = false; |
| mExtras = null; |
| return false; |
| } |
| |
| int hour, minute; |
| float second; |
| try { |
| hour = Integer.parseInt(time.substring(0, 2)); |
| minute = Integer.parseInt(time.substring(2, 4)); |
| second = Float.parseFloat(time.substring(4, time.length())); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Error parsing timestamp " + time); |
| return false; |
| } |
| |
| int isecond = (int) second; |
| int millis = (int) ((second - isecond) * 1000); |
| Calendar c = new GregorianCalendar(sUtcTimeZone); |
| c.set(mYear, mMonth, mDay, hour, minute, isecond); |
| long newTime = c.getTimeInMillis() + millis; |
| |
| if (mTime == -1) { |
| mTime = 0; |
| mBaseTime = newTime; |
| } |
| newTime -= mBaseTime; |
| |
| // If the timestamp has advanced, copy the temporary data |
| // into a new Location |
| if (newTime != mTime) { |
| mNewWaypoint = true; |
| mLocation = new Location(mName); |
| mLocation.setTime(mTime); |
| mLocation.setLatitude(mLatitude); |
| mLocation.setLongitude(mLongitude); |
| if (mHasAltitude) { |
| mLocation.setAltitude(mAltitude); |
| } |
| if (mHasBearing) { |
| mLocation.setBearing(mBearing); |
| } |
| if (mHasSpeed) { |
| mLocation.setSpeed(mSpeed); |
| } |
| mLocation.setExtras(mExtras); |
| mExtras = null; |
| |
| mTime = newTime; |
| mHasAltitude = false; |
| mHasBearing = false; |
| mHasSpeed = false; |
| } |
| return true; |
| } |
| |
| private boolean updateDate(String date) { |
| if (date.length() != 6) { |
| return false; |
| } |
| int month, day, year; |
| try { |
| day = Integer.parseInt(date.substring(0, 2)); |
| month = Integer.parseInt(date.substring(2, 4)); |
| year = 2000 + Integer.parseInt(date.substring(4, 6)); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Error parsing date " + date); |
| return false; |
| } |
| |
| mYear = year; |
| mMonth = month; |
| mDay = day; |
| return true; |
| } |
| |
| private boolean updateTime(String time, String date) { |
| if (!updateDate(date)) { |
| return false; |
| } |
| return updateTime(time); |
| } |
| |
| private boolean updateIntExtra(String name, String value) { |
| int val; |
| try { |
| val = Integer.parseInt(value); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Exception parsing int " + name + ": " + value, nfe); |
| return false; |
| } |
| if (mExtras == null) { |
| mExtras = new Bundle(); |
| } |
| mExtras.putInt(name, val); |
| return true; |
| } |
| |
| private boolean updateFloatExtra(String name, String value) { |
| float val; |
| try { |
| val = Float.parseFloat(value); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Exception parsing float " + name + ": " + value, nfe); |
| return false; |
| } |
| if (mExtras == null) { |
| mExtras = new Bundle(); |
| } |
| mExtras.putFloat(name, val); |
| return true; |
| } |
| |
| private boolean updateDoubleExtra(String name, String value) { |
| double val; |
| try { |
| val = Double.parseDouble(value); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Exception parsing double " + name + ": " + value, nfe); |
| return false; |
| } |
| if (mExtras == null) { |
| mExtras = new Bundle(); |
| } |
| mExtras.putDouble(name, val); |
| return true; |
| } |
| |
| private double convertFromHHMM(String coord) { |
| double val = Double.parseDouble(coord); |
| int degrees = ((int) Math.floor(val)) / 100; |
| double minutes = val - (degrees * 100); |
| double dcoord = degrees + minutes / 60.0; |
| return dcoord; |
| } |
| |
| private boolean updateLatLon(String latitude, String latitudeHemi, |
| String longitude, String longitudeHemi) { |
| if (latitude.length() == 0 || longitude.length() == 0) { |
| return false; |
| } |
| |
| // Lat/long values are expressed as {D}DDMM.MMMM |
| double lat, lon; |
| try { |
| lat = convertFromHHMM(latitude); |
| if (latitudeHemi.charAt(0) == 'S') { |
| lat = -lat; |
| } |
| } catch (NumberFormatException nfe1) { |
| Log.e(TAG, "Exception parsing lat/long: " + nfe1, nfe1); |
| return false; |
| } |
| |
| try { |
| lon = convertFromHHMM(longitude); |
| if (longitudeHemi.charAt(0) == 'W') { |
| lon = -lon; |
| } |
| } catch (NumberFormatException nfe2) { |
| Log.e(TAG, "Exception parsing lat/long: " + nfe2, nfe2); |
| return false; |
| } |
| |
| // Only update if both were parsed cleanly |
| mLatitude = lat; |
| mLongitude = lon; |
| return true; |
| } |
| |
| private boolean updateAltitude(String altitude) { |
| if (altitude.length() == 0) { |
| return false; |
| } |
| double alt; |
| try { |
| alt = Double.parseDouble(altitude); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Exception parsing altitude " + altitude + ": " + nfe, |
| nfe); |
| return false; |
| } |
| |
| mHasAltitude = true; |
| mAltitude = alt; |
| return true; |
| } |
| |
| private boolean updateBearing(String bearing) { |
| float brg; |
| try { |
| brg = Float.parseFloat(bearing); |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Exception parsing bearing " + bearing + ": " + nfe, |
| nfe); |
| return false; |
| } |
| |
| mHasBearing = true; |
| mBearing = brg; |
| return true; |
| } |
| |
| private boolean updateSpeed(String speed) { |
| float spd; |
| try { |
| spd = Float.parseFloat(speed) * KNOTS_TO_METERS_PER_SECOND; |
| } catch (NumberFormatException nfe) { |
| Log.e(TAG, "Exception parsing speed " + speed + ": " + nfe, nfe); |
| return false; |
| } |
| |
| mHasSpeed = true; |
| mSpeed = spd; |
| return true; |
| } |
| |
| public boolean parseSentence(String s) { |
| int len = s.length(); |
| if (len < 9) { |
| return false; |
| } |
| if (s.charAt(len - 3) == '*') { |
| // String checksum = s.substring(len - 4, len); |
| s = s.substring(0, len - 3); |
| } |
| String[] tokens = s.split(","); |
| String sentenceId = tokens[0].substring(3, 6); |
| |
| int idx = 1; |
| try { |
| if (sentenceId.equals("GGA")) { |
| String time = tokens[idx++]; |
| String latitude = tokens[idx++]; |
| String latitudeHemi = tokens[idx++]; |
| String longitude = tokens[idx++]; |
| String longitudeHemi = tokens[idx++]; |
| String fixQuality = tokens[idx++]; |
| String numSatellites = tokens[idx++]; |
| String horizontalDilutionOfPrecision = tokens[idx++]; |
| String altitude = tokens[idx++]; |
| String altitudeUnits = tokens[idx++]; |
| String heightOfGeoid = tokens[idx++]; |
| String heightOfGeoidUnits = tokens[idx++]; |
| String timeSinceLastDgpsUpdate = tokens[idx++]; |
| |
| updateTime(time); |
| updateLatLon(latitude, latitudeHemi, |
| longitude, longitudeHemi); |
| updateAltitude(altitude); |
| // updateQuality(fixQuality); |
| updateIntExtra("numSatellites", numSatellites); |
| updateFloatExtra("hdop", horizontalDilutionOfPrecision); |
| |
| if (mNewWaypoint) { |
| mNewWaypoint = false; |
| return true; |
| } |
| } else if (sentenceId.equals("GSA")) { |
| // DOP and active satellites |
| String selectionMode = tokens[idx++]; // m=manual, a=auto 2d/3d |
| String mode = tokens[idx++]; // 1=no fix, 2=2d, 3=3d |
| for (int i = 0; i < 12; i++) { |
| String id = tokens[idx++]; |
| } |
| String pdop = tokens[idx++]; |
| String hdop = tokens[idx++]; |
| String vdop = tokens[idx++]; |
| |
| // TODO - publish satellite ids |
| updateFloatExtra("pdop", pdop); |
| updateFloatExtra("hdop", hdop); |
| updateFloatExtra("vdop", vdop); |
| } else if (sentenceId.equals("GSV")) { |
| // Satellites in view |
| String numMessages = tokens[idx++]; |
| String messageNum = tokens[idx++]; |
| String svsInView = tokens[idx++]; |
| for (int i = 0; i < 4; i++) { |
| if (idx + 2 < tokens.length) { |
| String prnNumber = tokens[idx++]; |
| String elevation = tokens[idx++]; |
| String azimuth = tokens[idx++]; |
| if (idx < tokens.length) { |
| String snr = tokens[idx++]; |
| } |
| } |
| } |
| // TODO - publish this info |
| } else if (sentenceId.equals("RMC")) { |
| // Recommended minimum navigation information |
| String time = tokens[idx++]; |
| String fixStatus = tokens[idx++]; |
| String latitude = tokens[idx++]; |
| String latitudeHemi = tokens[idx++]; |
| String longitude = tokens[idx++]; |
| String longitudeHemi = tokens[idx++]; |
| String speed = tokens[idx++]; |
| String bearing = tokens[idx++]; |
| String utcDate = tokens[idx++]; |
| String magneticVariation = tokens[idx++]; |
| String magneticVariationDir = tokens[idx++]; |
| String mode = tokens[idx++]; |
| |
| if (fixStatus.charAt(0) == 'A') { |
| updateTime(time, utcDate); |
| updateLatLon(latitude, latitudeHemi, |
| longitude, longitudeHemi); |
| updateBearing(bearing); |
| updateSpeed(speed); |
| } |
| |
| if (mNewWaypoint) { |
| return true; |
| } |
| } else { |
| Log.e(TAG, "Unknown sentence: " + s); |
| } |
| } catch (ArrayIndexOutOfBoundsException e) { |
| // do nothing - sentence will have no effect |
| Log.e(TAG, "AIOOBE", e); |
| |
| for (int i = 0; i < tokens.length; i++) { |
| Log.e(TAG, "Got token #" + i + " = " + tokens[i]); |
| } |
| } |
| |
| return false; |
| } |
| |
| // } else if (sentenceId.equals("GLL")) { |
| // // Geographics position lat/long |
| // String latitude = tokens[idx++]; |
| // String latitudeHemi = tokens[idx++]; |
| // String longitude = tokens[idx++]; |
| // String longitudeHemi = tokens[idx++]; |
| // String time = tokens[idx++]; |
| // String status = tokens[idx++]; |
| // String mode = tokens[idx++]; |
| // String checksum = tokens[idx++]; |
| // |
| // if (status.charAt(0) == 'A') { |
| // updateTime(time); |
| // updateLatLon(latitude, latitudeHemi, longitude, longitudeHemi); |
| // } |
| //} else if (sentenceId.equals("VTG")) { |
| // String trackMadeGood = tokens[idx++]; |
| // String t = tokens[idx++]; |
| // String unused1 = tokens[idx++]; |
| // String unused2 = tokens[idx++]; |
| // String groundSpeedKnots = tokens[idx++]; |
| // String n = tokens[idx++]; |
| // String groundSpeedKph = tokens[idx++]; |
| // String k = tokens[idx++]; |
| // String checksum = tokens[idx++]; |
| // |
| // updateSpeed(groundSpeedKph); |
| |
| public Location getLocation() { |
| return mLocation; |
| } |
| } |