• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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