src/main/java/de/blau/android/services/util/Nmea.java
/**
     * Minimal parsing of VTG, GGA and GNS NMEA 0183 sentences
     * 
     * Some filtering is applied to only use the best sources and only one sentence type
     * 
     * This method is not thread safe
     * 
     * @param sentence the NMEA sentence including checksum
     * @param nmeaLocation the ExtendedLocation to update and return
     * @return an ExtendedLocation object if we have a valid new fix otherwise null
     */
Minimal parsing of VTG, GGA and GNS NMEA 0183 sentences

Some filtering is applied to only use the best sources and only one sentence type

This method is not thread safe

@param sentence the NMEA sentence including checksum
@param nmeaLocation the ExtendedLocation to update and return
@return an ExtendedLocation object if we have a valid new fix otherwise null
Minimal parsing of VTG GGA and GNS NMEA 0183 sentences Some filtering is applied to only use the best sources and only one sentence type This method is not thread safe @param sentence the NMEA sentence including checksum @param nmeaLocation the ExtendedLocation to update and return @return an ExtendedLocation object if we have a valid new fix otherwise null
@Nullable
    public static ExtendedLocation processSentence(@NonNull String sentence, @NonNull ExtendedLocation nmeaLocation) {
        boolean posUpdate = false;
        try {
            int length = sentence.length();
            if (length > 9) { 
                int star = sentence.indexOf('*');
                if (star > 5 && length >= star + 3) {
                    String withoutChecksum = sentence.substring(1, star);
                    int receivedChecksum = Integer.parseInt(sentence.substring(star + 1, star + 3), 16);
                    if (receivedChecksum == checksum(withoutChecksum)) {
                        String talker = withoutChecksum.substring(0, 2);
                        Integer talkerPrio = TALKER_PRIORITY.get(talker);
                        if (talkerPrio == null) { 
                            return null;
                        }
                        String sentenceType = withoutChecksum.substring(2, 5);
                        Integer currentSentencePrio = SENTENCE_PRIORITY.get(sentenceType);
                        if (currentSentencePrio == null) { 
                            return null;
                        }
                        double lat = Double.NaN;
                        double lon = Double.NaN;
                        double hdop = Double.NaN;
                        double fauxAccuracy = Double.NaN;
                        double geoidHeight = Double.NaN;
                        double geoidCorrection = Double.NaN;
                        switch (sentenceType) {
                        case GNS_SENTENCE:
                            String[] values = withoutChecksum.split(",", -12); 
                            if (values.length == 13) {
                                String fixType = values[GNS_FIX_TYPE].toUpperCase(Locale.US);
                                if ((GN.equals(talker) && (!fixType.startsWith("NN")) || !"N".equals(fixType))
                                        && Integer.parseInt(values[GNS_SAT_COUNT]) >= 4) {
                                    lat = latFromNmea(values);
                                    lon = lonFromNmea(values);
                                    hdop = Double.parseDouble(values[GNS_HDOP]);
                                    geoidHeight = Double.parseDouble(values[GNS_HEIGHT]);
                                    geoidCorrection = Double.parseDouble(values[GNS_GEOID_CORRECTION]);
                                    posUpdate = true;
                                }
                            } else {
                                throw new UnsupportedFormatException(Integer.toString(values.length));
                            }
                            break;
                        case GGA_SENTENCE:
                            values = withoutChecksum.split(",", -14);
                            if (values.length == 15) {
                                if (!"0".equals(values[GGA_FIX_TYPE]) && Integer.parseInt(values[GGA_SAT_COUNT]) >= 4) {
                                    lat = latFromNmea(values);
                                    lon = lonFromNmea(values);
                                    hdop = Double.parseDouble(values[GGA_HDOP]);
                                    geoidHeight = Double.parseDouble(values[GGA_HEIGHT]);
                                    geoidCorrection = Double.parseDouble(values[GGA_GEOID_CORRECTION]);
                                    int fixType = Integer.parseInt(values[GGA_FIX_TYPE]);
                                    switch (fixType) {
                                    case GGA_FIX_AUTONOMOUS:
                                        fauxAccuracy = GGA_FIX_AUTONOMOUS_MUL * hdop;
                                        break;
                                    case GGA_FIX_DIFFERENTIAL:
                                        fauxAccuracy = GGA_FIX_DIFFERENTIAL_MUL * hdop;
                                        break;
                                    case GGA_FIX_RTK_FIXED:
                                        fauxAccuracy = GGA_FIX_RTK_FIXED_MUL * hdop;
                                        break;
                                    case GGA_FIX_RTK_FLOAT:
                                        fauxAccuracy = GGA_FIX_RTK_FLOAT_MUL * hdop;
                                        break;
                                    default:
                                        fauxAccuracy = 50.0;
                                        break;
                                    }
                                    posUpdate = true;
                                }
                            } else {
                                throw new UnsupportedFormatException(Integer.toString(values.length));
                            }
                            break;
                        case VTG_SENTENCE:
                            values = withoutChecksum.split(",", -11);
                            if (values.length == 12) {
                                if (!values[9].toUpperCase(Locale.US).startsWith("N")) {
                                    double course = Double.parseDouble(values[VTG_COURSE]);
                                    nmeaLocation.setBearing((float) course);
                                    double speed = Double.parseDouble(values[VTG_SPEED]);
                                    nmeaLocation.setSpeed((float) (speed / 3.6D));
                                }
                            } else {
                                throw new UnsupportedFormatException(Integer.toString(values.length));
                            }
                            break;
                        default:
                            , we should never get here
                            return null;
                        }
                        if (posUpdate) {
                            if (hdop > HDOP_CUTOFF) {
                                return null;
                            }
                            if (talkerPrio > systemPriority) {
                                systemPriority = talkerPrio;
                            } else if (talkerPrio < systemPriority) { 
                                return null;
                            }
                            if (currentSentencePrio > sentencePriority) {
                                sentencePriority = currentSentencePrio;
                            } else if (currentSentencePrio < sentencePriority) { 
                                return null;
                            }
                            nmeaLocation.setGeoidHeight(geoidHeight);
                            nmeaLocation.setLatitude(lat);
                            nmeaLocation.setLongitude(lon);
                            nmeaLocation.setHdop(hdop);
                            if (fauxAccuracy != Double.NaN) {
                                nmeaLocation.setAccuracy((float) fauxAccuracy);
                            }
                            nmeaLocation.setGeoidCorrection(geoidCorrection);
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                                nmeaLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
                            }
                            nmeaLocation.setTime(System.currentTimeMillis());
                            return nmeaLocation;
                        }
                    }
                }
            }
        } catch (NumberFormatException e) {
            Log.d(DEBUG_TAG, "Invalid number format in " + sentence);
            return null;
        } catch (UnsupportedFormatException e) {
            Log.d(DEBUG_TAG, "Invalid number " + e.getMessage() + " of values in " + sentence);
            return null;
        } catch (Exception e) { 
            Log.e(DEBUG_TAG, "NMEA sentence " + sentence + " caused exception " + e);
            ACRAHelper.nocrashReport(e, e.getMessage());
        }
        return null;
    }
@ Nullable public static ExtendedLocation processSentence ( @ NonNull String sentence , @ NonNull ExtendedLocation nmeaLocation ) { boolean posUpdate = false ; try { int length = sentence . length ( ) ; if ( length > 9 ) { int star = sentence . indexOf ( '*' ) ; if ( star > 5 && length >= star + 3 ) { String withoutChecksum = sentence . substring ( 1 , star ) ; int receivedChecksum = Integer . parseInt ( sentence . substring ( star + 1 , star + 3 ) , 16 ) ; if ( receivedChecksum == checksum ( withoutChecksum ) ) { String talker = withoutChecksum . substring ( 0 , 2 ) ; Integer talkerPrio = TALKER_PRIORITY . get ( talker ) ; if ( talkerPrio == null ) { return null ; } String sentenceType = withoutChecksum . substring ( 2 , 5 ) ; Integer currentSentencePrio = SENTENCE_PRIORITY . get ( sentenceType ) ; if ( currentSentencePrio == null ) { return null ; } double lat = Double . NaN ; double lon = Double . NaN ; double hdop = Double . NaN ; double fauxAccuracy = Double . NaN ; double geoidHeight = Double . NaN ; double geoidCorrection = Double . NaN ; switch ( sentenceType ) { case GNS_SENTENCE : String [ ] values = withoutChecksum . split ( "," , - 12 ) ; if ( values . length == 13 ) { String fixType = values [ GNS_FIX_TYPE ] . toUpperCase ( Locale . US ) ; if ( ( GN . equals ( talker ) && ( ! fixType . startsWith ( "NN" ) ) || ! "N" . equals ( fixType ) ) && Integer . parseInt ( values [ GNS_SAT_COUNT ] ) >= 4 ) { lat = latFromNmea ( values ) ; lon = lonFromNmea ( values ) ; hdop = Double . parseDouble ( values [ GNS_HDOP ] ) ; geoidHeight = Double . parseDouble ( values [ GNS_HEIGHT ] ) ; geoidCorrection = Double . parseDouble ( values [ GNS_GEOID_CORRECTION ] ) ; posUpdate = true ; } } else { throw new UnsupportedFormatException ( Integer . toString ( values . length ) ) ; } break ; case GGA_SENTENCE : values = withoutChecksum . split ( "," , - 14 ) ; if ( values . length == 15 ) { if ( ! "0" . equals ( values [ GGA_FIX_TYPE ] ) && Integer . parseInt ( values [ GGA_SAT_COUNT ] ) >= 4 ) { lat = latFromNmea ( values ) ; lon = lonFromNmea ( values ) ; hdop = Double . parseDouble ( values [ GGA_HDOP ] ) ; geoidHeight = Double . parseDouble ( values [ GGA_HEIGHT ] ) ; geoidCorrection = Double . parseDouble ( values [ GGA_GEOID_CORRECTION ] ) ; int fixType = Integer . parseInt ( values [ GGA_FIX_TYPE ] ) ; switch ( fixType ) { case GGA_FIX_AUTONOMOUS : fauxAccuracy = GGA_FIX_AUTONOMOUS_MUL * hdop ; break ; case GGA_FIX_DIFFERENTIAL : fauxAccuracy = GGA_FIX_DIFFERENTIAL_MUL * hdop ; break ; case GGA_FIX_RTK_FIXED : fauxAccuracy = GGA_FIX_RTK_FIXED_MUL * hdop ; break ; case GGA_FIX_RTK_FLOAT : fauxAccuracy = GGA_FIX_RTK_FLOAT_MUL * hdop ; break ; default : fauxAccuracy = 50.0 ; break ; } posUpdate = true ; } } else { throw new UnsupportedFormatException ( Integer . toString ( values . length ) ) ; } break ; case VTG_SENTENCE : values = withoutChecksum . split ( "," , - 11 ) ; if ( values . length == 12 ) { if ( ! values [ 9 ] . toUpperCase ( Locale . US ) . startsWith ( "N" ) ) { double course = Double . parseDouble ( values [ VTG_COURSE ] ) ; nmeaLocation . setBearing ( ( float ) course ) ; double speed = Double . parseDouble ( values [ VTG_SPEED ] ) ; nmeaLocation . setSpeed ( ( float ) ( speed / 3.6D ) ) ; } } else { throw new UnsupportedFormatException ( Integer . toString ( values . length ) ) ; } break ; default : return null ; } if ( posUpdate ) { if ( hdop > HDOP_CUTOFF ) { return null ; } if ( talkerPrio > systemPriority ) { systemPriority = talkerPrio ; } else if ( talkerPrio < systemPriority ) { return null ; } if ( currentSentencePrio > sentencePriority ) { sentencePriority = currentSentencePrio ; } else if ( currentSentencePrio < sentencePriority ) { return null ; } nmeaLocation . setGeoidHeight ( geoidHeight ) ; nmeaLocation . setLatitude ( lat ) ; nmeaLocation . setLongitude ( lon ) ; nmeaLocation . setHdop ( hdop ) ; if ( fauxAccuracy != Double . NaN ) { nmeaLocation . setAccuracy ( ( float ) fauxAccuracy ) ; } nmeaLocation . setGeoidCorrection ( geoidCorrection ) ; if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . JELLY_BEAN_MR1 ) { nmeaLocation . setElapsedRealtimeNanos ( SystemClock . elapsedRealtimeNanos ( ) ) ; } nmeaLocation . setTime ( System . currentTimeMillis ( ) ) ; return nmeaLocation ; } } } } } catch ( NumberFormatException e ) { Log . d ( DEBUG_TAG , "Invalid number format in " + sentence ) ; return null ; } catch ( UnsupportedFormatException e ) { Log . d ( DEBUG_TAG , "Invalid number " + e . getMessage ( ) + " of values in " + sentence ) ; return null ; } catch ( Exception e ) { Log . e ( DEBUG_TAG , "NMEA sentence " + sentence + " caused exception " + e ) ; ACRAHelper . nocrashReport ( e , e . getMessage ( ) ) ; } return null ; }
Minimal parsing of VTG, GGA and GNS NMEA 0183 sentences
Minimal parsing of VTG GGA and GNS NMEA 0183 sentences
// everything shorter is invalid // unsupported talker // unsupported sentence // java magic // at least 4 satellites // at least one "good" system needs a // fix // we need a fix // Extract fix type and create faux accuracy. The key idea is to // 1) give some sort of not-crazy accuracy in non-RTK mode // 2) give an accuracy in RTK which is arguably reasonable, and also a signal // to the user about fixed vs float vs not-RTK. // Should not happen. Indicate poor accuracy, without log spam. // unsupported sentence, we should never get here // hdop filtering // system filtering // low prio talkers ignored // sentence filtering // low prio sentences ignored // we only really need to know this for determining how old the fix is // anything else