1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.location; 18 19 import java.util.Calendar; 20 import java.util.GregorianCalendar; 21 import java.util.TimeZone; 22 23 import android.location.Location; 24 import android.os.Bundle; 25 import android.util.Log; 26 27 /** 28 * {@hide} 29 */ 30 public class NmeaParser { 31 32 private static final String TAG = "NmeaParser"; 33 34 private static final TimeZone sUtcTimeZone = TimeZone.getTimeZone("UTC"); 35 36 private static final float KNOTS_TO_METERS_PER_SECOND = 0.51444444444f; 37 38 private final String mName; 39 40 private int mYear = -1; 41 private int mMonth; 42 private int mDay; 43 44 private long mTime = -1; 45 private long mBaseTime; 46 private double mLatitude; 47 private double mLongitude; 48 49 private boolean mHasAltitude; 50 private double mAltitude; 51 private boolean mHasBearing; 52 private float mBearing; 53 private boolean mHasSpeed; 54 private float mSpeed; 55 56 private boolean mNewWaypoint = false; 57 private Location mLocation = null; 58 private Bundle mExtras; 59 NmeaParser(String name)60 public NmeaParser(String name) { 61 mName = name; 62 } 63 updateTime(String time)64 private boolean updateTime(String time) { 65 if (time.length() < 6) { 66 return false; 67 } 68 if (mYear == -1) { 69 // Since we haven't seen a day/month/year yet, 70 // we can't construct a meaningful time stamp. 71 // Clean up any old data. 72 mLatitude = 0.0; 73 mLongitude = 0.0; 74 mHasAltitude = false; 75 mHasBearing = false; 76 mHasSpeed = false; 77 mExtras = null; 78 return false; 79 } 80 81 int hour, minute; 82 float second; 83 try { 84 hour = Integer.parseInt(time.substring(0, 2)); 85 minute = Integer.parseInt(time.substring(2, 4)); 86 second = Float.parseFloat(time.substring(4, time.length())); 87 } catch (NumberFormatException nfe) { 88 Log.e(TAG, "Error parsing timestamp " + time); 89 return false; 90 } 91 92 int isecond = (int) second; 93 int millis = (int) ((second - isecond) * 1000); 94 Calendar c = new GregorianCalendar(sUtcTimeZone); 95 c.set(mYear, mMonth, mDay, hour, minute, isecond); 96 long newTime = c.getTimeInMillis() + millis; 97 98 if (mTime == -1) { 99 mTime = 0; 100 mBaseTime = newTime; 101 } 102 newTime -= mBaseTime; 103 104 // If the timestamp has advanced, copy the temporary data 105 // into a new Location 106 if (newTime != mTime) { 107 mNewWaypoint = true; 108 mLocation = new Location(mName); 109 mLocation.setTime(mTime); 110 mLocation.setLatitude(mLatitude); 111 mLocation.setLongitude(mLongitude); 112 if (mHasAltitude) { 113 mLocation.setAltitude(mAltitude); 114 } 115 if (mHasBearing) { 116 mLocation.setBearing(mBearing); 117 } 118 if (mHasSpeed) { 119 mLocation.setSpeed(mSpeed); 120 } 121 mLocation.setExtras(mExtras); 122 mExtras = null; 123 124 mTime = newTime; 125 mHasAltitude = false; 126 mHasBearing = false; 127 mHasSpeed = false; 128 } 129 return true; 130 } 131 updateDate(String date)132 private boolean updateDate(String date) { 133 if (date.length() != 6) { 134 return false; 135 } 136 int month, day, year; 137 try { 138 day = Integer.parseInt(date.substring(0, 2)); 139 month = Integer.parseInt(date.substring(2, 4)); 140 year = 2000 + Integer.parseInt(date.substring(4, 6)); 141 } catch (NumberFormatException nfe) { 142 Log.e(TAG, "Error parsing date " + date); 143 return false; 144 } 145 146 mYear = year; 147 mMonth = month; 148 mDay = day; 149 return true; 150 } 151 updateTime(String time, String date)152 private boolean updateTime(String time, String date) { 153 if (!updateDate(date)) { 154 return false; 155 } 156 return updateTime(time); 157 } 158 updateIntExtra(String name, String value)159 private boolean updateIntExtra(String name, String value) { 160 int val; 161 try { 162 val = Integer.parseInt(value); 163 } catch (NumberFormatException nfe) { 164 Log.e(TAG, "Exception parsing int " + name + ": " + value, nfe); 165 return false; 166 } 167 if (mExtras == null) { 168 mExtras = new Bundle(); 169 } 170 mExtras.putInt(name, val); 171 return true; 172 } 173 updateFloatExtra(String name, String value)174 private boolean updateFloatExtra(String name, String value) { 175 float val; 176 try { 177 val = Float.parseFloat(value); 178 } catch (NumberFormatException nfe) { 179 Log.e(TAG, "Exception parsing float " + name + ": " + value, nfe); 180 return false; 181 } 182 if (mExtras == null) { 183 mExtras = new Bundle(); 184 } 185 mExtras.putFloat(name, val); 186 return true; 187 } 188 updateDoubleExtra(String name, String value)189 private boolean updateDoubleExtra(String name, String value) { 190 double val; 191 try { 192 val = Double.parseDouble(value); 193 } catch (NumberFormatException nfe) { 194 Log.e(TAG, "Exception parsing double " + name + ": " + value, nfe); 195 return false; 196 } 197 if (mExtras == null) { 198 mExtras = new Bundle(); 199 } 200 mExtras.putDouble(name, val); 201 return true; 202 } 203 convertFromHHMM(String coord)204 private double convertFromHHMM(String coord) { 205 double val = Double.parseDouble(coord); 206 int degrees = ((int) Math.floor(val)) / 100; 207 double minutes = val - (degrees * 100); 208 double dcoord = degrees + minutes / 60.0; 209 return dcoord; 210 } 211 updateLatLon(String latitude, String latitudeHemi, String longitude, String longitudeHemi)212 private boolean updateLatLon(String latitude, String latitudeHemi, 213 String longitude, String longitudeHemi) { 214 if (latitude.length() == 0 || longitude.length() == 0) { 215 return false; 216 } 217 218 // Lat/long values are expressed as {D}DDMM.MMMM 219 double lat, lon; 220 try { 221 lat = convertFromHHMM(latitude); 222 if (latitudeHemi.charAt(0) == 'S') { 223 lat = -lat; 224 } 225 } catch (NumberFormatException nfe1) { 226 Log.e(TAG, "Exception parsing lat/long: " + nfe1, nfe1); 227 return false; 228 } 229 230 try { 231 lon = convertFromHHMM(longitude); 232 if (longitudeHemi.charAt(0) == 'W') { 233 lon = -lon; 234 } 235 } catch (NumberFormatException nfe2) { 236 Log.e(TAG, "Exception parsing lat/long: " + nfe2, nfe2); 237 return false; 238 } 239 240 // Only update if both were parsed cleanly 241 mLatitude = lat; 242 mLongitude = lon; 243 return true; 244 } 245 updateAltitude(String altitude)246 private boolean updateAltitude(String altitude) { 247 if (altitude.length() == 0) { 248 return false; 249 } 250 double alt; 251 try { 252 alt = Double.parseDouble(altitude); 253 } catch (NumberFormatException nfe) { 254 Log.e(TAG, "Exception parsing altitude " + altitude + ": " + nfe, 255 nfe); 256 return false; 257 } 258 259 mHasAltitude = true; 260 mAltitude = alt; 261 return true; 262 } 263 updateBearing(String bearing)264 private boolean updateBearing(String bearing) { 265 float brg; 266 try { 267 brg = Float.parseFloat(bearing); 268 } catch (NumberFormatException nfe) { 269 Log.e(TAG, "Exception parsing bearing " + bearing + ": " + nfe, 270 nfe); 271 return false; 272 } 273 274 mHasBearing = true; 275 mBearing = brg; 276 return true; 277 } 278 updateSpeed(String speed)279 private boolean updateSpeed(String speed) { 280 float spd; 281 try { 282 spd = Float.parseFloat(speed) * KNOTS_TO_METERS_PER_SECOND; 283 } catch (NumberFormatException nfe) { 284 Log.e(TAG, "Exception parsing speed " + speed + ": " + nfe, nfe); 285 return false; 286 } 287 288 mHasSpeed = true; 289 mSpeed = spd; 290 return true; 291 } 292 parseSentence(String s)293 public boolean parseSentence(String s) { 294 int len = s.length(); 295 if (len < 9) { 296 return false; 297 } 298 if (s.charAt(len - 3) == '*') { 299 // String checksum = s.substring(len - 4, len); 300 s = s.substring(0, len - 3); 301 } 302 String[] tokens = s.split(","); 303 String sentenceId = tokens[0].substring(3, 6); 304 305 int idx = 1; 306 try { 307 if (sentenceId.equals("GGA")) { 308 String time = tokens[idx++]; 309 String latitude = tokens[idx++]; 310 String latitudeHemi = tokens[idx++]; 311 String longitude = tokens[idx++]; 312 String longitudeHemi = tokens[idx++]; 313 String fixQuality = tokens[idx++]; 314 String numSatellites = tokens[idx++]; 315 String horizontalDilutionOfPrecision = tokens[idx++]; 316 String altitude = tokens[idx++]; 317 String altitudeUnits = tokens[idx++]; 318 String heightOfGeoid = tokens[idx++]; 319 String heightOfGeoidUnits = tokens[idx++]; 320 String timeSinceLastDgpsUpdate = tokens[idx++]; 321 322 updateTime(time); 323 updateLatLon(latitude, latitudeHemi, 324 longitude, longitudeHemi); 325 updateAltitude(altitude); 326 // updateQuality(fixQuality); 327 updateIntExtra("numSatellites", numSatellites); 328 updateFloatExtra("hdop", horizontalDilutionOfPrecision); 329 330 if (mNewWaypoint) { 331 mNewWaypoint = false; 332 return true; 333 } 334 } else if (sentenceId.equals("GSA")) { 335 // DOP and active satellites 336 String selectionMode = tokens[idx++]; // m=manual, a=auto 2d/3d 337 String mode = tokens[idx++]; // 1=no fix, 2=2d, 3=3d 338 for (int i = 0; i < 12; i++) { 339 String id = tokens[idx++]; 340 } 341 String pdop = tokens[idx++]; 342 String hdop = tokens[idx++]; 343 String vdop = tokens[idx++]; 344 345 // TODO - publish satellite ids 346 updateFloatExtra("pdop", pdop); 347 updateFloatExtra("hdop", hdop); 348 updateFloatExtra("vdop", vdop); 349 } else if (sentenceId.equals("GSV")) { 350 // Satellites in view 351 String numMessages = tokens[idx++]; 352 String messageNum = tokens[idx++]; 353 String svsInView = tokens[idx++]; 354 for (int i = 0; i < 4; i++) { 355 if (idx + 2 < tokens.length) { 356 String prnNumber = tokens[idx++]; 357 String elevation = tokens[idx++]; 358 String azimuth = tokens[idx++]; 359 if (idx < tokens.length) { 360 String snr = tokens[idx++]; 361 } 362 } 363 } 364 // TODO - publish this info 365 } else if (sentenceId.equals("RMC")) { 366 // Recommended minimum navigation information 367 String time = tokens[idx++]; 368 String fixStatus = tokens[idx++]; 369 String latitude = tokens[idx++]; 370 String latitudeHemi = tokens[idx++]; 371 String longitude = tokens[idx++]; 372 String longitudeHemi = tokens[idx++]; 373 String speed = tokens[idx++]; 374 String bearing = tokens[idx++]; 375 String utcDate = tokens[idx++]; 376 String magneticVariation = tokens[idx++]; 377 String magneticVariationDir = tokens[idx++]; 378 String mode = tokens[idx++]; 379 380 if (fixStatus.charAt(0) == 'A') { 381 updateTime(time, utcDate); 382 updateLatLon(latitude, latitudeHemi, 383 longitude, longitudeHemi); 384 updateBearing(bearing); 385 updateSpeed(speed); 386 } 387 388 if (mNewWaypoint) { 389 return true; 390 } 391 } else { 392 Log.e(TAG, "Unknown sentence: " + s); 393 } 394 } catch (ArrayIndexOutOfBoundsException e) { 395 // do nothing - sentence will have no effect 396 Log.e(TAG, "AIOOBE", e); 397 398 for (int i = 0; i < tokens.length; i++) { 399 Log.e(TAG, "Got token #" + i + " = " + tokens[i]); 400 } 401 } 402 403 return false; 404 } 405 406 // } else if (sentenceId.equals("GLL")) { 407 // // Geographics position lat/long 408 // String latitude = tokens[idx++]; 409 // String latitudeHemi = tokens[idx++]; 410 // String longitude = tokens[idx++]; 411 // String longitudeHemi = tokens[idx++]; 412 // String time = tokens[idx++]; 413 // String status = tokens[idx++]; 414 // String mode = tokens[idx++]; 415 // String checksum = tokens[idx++]; 416 // 417 // if (status.charAt(0) == 'A') { 418 // updateTime(time); 419 // updateLatLon(latitude, latitudeHemi, longitude, longitudeHemi); 420 // } 421 //} else if (sentenceId.equals("VTG")) { 422 // String trackMadeGood = tokens[idx++]; 423 // String t = tokens[idx++]; 424 // String unused1 = tokens[idx++]; 425 // String unused2 = tokens[idx++]; 426 // String groundSpeedKnots = tokens[idx++]; 427 // String n = tokens[idx++]; 428 // String groundSpeedKph = tokens[idx++]; 429 // String k = tokens[idx++]; 430 // String checksum = tokens[idx++]; 431 // 432 // updateSpeed(groundSpeedKph); 433 getLocation()434 public Location getLocation() { 435 return mLocation; 436 } 437 } 438