com.inktomi.wxmetar.MetarParser.java Source code

Java tutorial

Introduction

Here is the source code for com.inktomi.wxmetar.MetarParser.java

Source

/*
 * Copyright (c) 2011.
 *
 * You are free:
 * to Share  to copy, distribute and transmit the work
 * to Remix  to adapt the work
 *
 *
 * Under the following conditions:
 * Attribution  You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
 *
 * Noncommercial  You may not use this work for commercial purposes.
 *
 * Share Alike  If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one.
 *
 * For more information, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
 */

package com.inktomi.wxmetar;

import com.inktomi.wxmetar.metar.Cloud;
import com.inktomi.wxmetar.metar.Metar;
import com.inktomi.wxmetar.metar.PresentWeather;
import org.apache.commons.lang.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MetarParser {
    private static Pattern NUMBER = Pattern.compile("^(\\d+)$");
    private static Pattern FRACTION = Pattern.compile("^(?:(\\d+) */ *(\\d+))SM$");

    public static Metar parseMetar(final String input) {
        Metar finalMetar = new Metar();

        // Trim any extra space off the input
        String inputMetar = StringUtils.trimToEmpty(input);

        // Just to be sure.
        inputMetar = inputMetar.toUpperCase();

        // Split the metar on spaces
        String[] tokens = StringUtils.split(inputMetar);

        if (null != tokens) {
            finalMetar = parseTokens(tokens);
        }

        return finalMetar;
    }

    /**
     * Parses a String[] of metar parts into a Metar
     * <p/>
     * KCLE 050551Z 17012G24KT 10SM SCT070 SCT085 OVC110 09/03 A3005 RMK AO2 SLP180 8/57/ 60004 T00940028 10094 20061 56026
     * KL35 051752Z AUTO 26006KT 10SM CLR 11/M19 A3030 RMK AO2
     *
     * @param tokens the tokens to parse
     * @return the assembled Metar
     */
    private static Metar parseTokens(final String[] tokens) {
        Metar rval = new Metar();

        rval.station = tokens[0];

        // The day of the month is the first 2
        rval.dayOfMonth = Integer.parseInt(StringUtils.substring(tokens[1], 0, 2));

        // The time is from 0 to length-1
        rval.zuluHour = Integer.parseInt(StringUtils.substring(tokens[1], 2, tokens[1].length() - 3));
        rval.zuluMinute = Integer.parseInt(StringUtils.substring(tokens[1], 4, tokens[1].length() - 1));

        // We start our actual parsing at the third element
        for (int i = 2; i < tokens.length; i++) {
            String token = tokens[i];
            String nextToken = null;
            if (tokens.length > i + 1) {
                nextToken = tokens[i + 1];
            }

            // First, we have AUTO or COR
            if (parseModifier(token, rval)) {
                continue;
            }

            if (parseWinds(token, rval)) {
                continue;
            }

            if (null != nextToken && parseVisibility(token, nextToken, rval)) {
                Matcher numberMatcher = NUMBER.matcher(token);
                Matcher fractionMatcher = FRACTION.matcher(nextToken);

                if (!StringUtils.endsWith(token, "SM") && numberMatcher.matches()
                        && StringUtils.endsWith(nextToken, "SM") && fractionMatcher.matches()) {

                    // Increment i one here since we had a fraction: 1 1/2SM
                    i = i + 1;
                }

                continue;
            }

            if (parsePresentWeather(token, rval)) {
                continue;
            }

            if (parseClouds(token, rval)) {
                continue;
            }

            if (parseTempDewpoint(token, rval)) {
                continue;
            }

            if (parseAltimeter(token, rval)) {
                continue;
            }

        }

        return rval;
    }

    // Returns true if we found the item we were looking for to break the loop
    static boolean parseModifier(String token, final Metar metar) {
        boolean rval = Boolean.FALSE;

        if (StringUtils.equals(token, "AUTO")) {
            metar.auto = Boolean.TRUE;
            rval = Boolean.TRUE;
        }

        if (StringUtils.equals(token, "COR")) {
            metar.auto = Boolean.TRUE;
            rval = Boolean.TRUE;
        }

        return rval;
    }

    // 26006KT, 17012G24KT, VRB03KT
    static boolean parseWinds(String token, final Metar metar) {
        boolean rval = Boolean.FALSE;

        // Winds will be the only element that ends in knots
        if (StringUtils.endsWith(token, "KT")) {

            // At this point, we know we should handle this token
            rval = Boolean.TRUE;

            // Remove the KT
            token = StringUtils.remove(token, "KT");

            // Is it variable?
            if (StringUtils.startsWith(token, "VRB")) {
                metar.winds.variable = Boolean.TRUE;

                // Trim of the VRB
                token = StringUtils.remove(token, "VRB");

                metar.winds.windSpeed = Float.parseFloat(token);

                // Stop processing this token
                return rval;
            }

            // At this point, we know the first 3 chars are wind direction
            metar.winds.windDirection = Integer.parseInt(StringUtils.substring(token, 0, 3));

            // Is it gusting? 17012G24
            int postionOfG = StringUtils.indexOf(token, "G");
            if (postionOfG > -1) {
                metar.winds.windGusts = Float
                        .parseFloat(StringUtils.substring(token, postionOfG + 1, token.length()));
                metar.winds.windSpeed = Float.parseFloat(StringUtils.substring(token, 3, postionOfG));
            }

            // Is it just a normal wind measurement?
            // 26006
            if (postionOfG == -1 && !metar.winds.variable) {
                metar.winds.windSpeed = Float.parseFloat(StringUtils.substring(token, 2, token.length()));
            }
        }

        return rval;
    }

    // 1 1/2SM (damnit)
    // 10SM
    static boolean parseVisibility(String token, String nextToken, final Metar metar) {
        boolean rval = Boolean.FALSE;

        Matcher numberMatcher = NUMBER.matcher(token);
        Matcher fractionMatcher = FRACTION.matcher(nextToken);

        // Check for that fraction
        if (!StringUtils.endsWith(token, "SM") && numberMatcher.matches() && StringUtils.endsWith(nextToken, "SM")
                && fractionMatcher.matches()) {

            // add them together
            float visMiles = Float.parseFloat(token); // this should be something like 1 or 2

            // Assemble the fraction
            float visFraction = Float.parseFloat(fractionMatcher.group(1))
                    / Float.parseFloat(fractionMatcher.group(2));

            metar.visibility = visMiles + visFraction;

            rval = Boolean.TRUE;
        }

        // Get the SM out of the way
        if (StringUtils.endsWith(token, "SM")) {
            metar.visibility = Float.parseFloat(StringUtils.substring(token, 0, token.length() - 2));
        }

        return rval;
    }

    static boolean parsePresentWeather(String token, final Metar metar) {
        boolean rval = Boolean.FALSE;

        String qualifier = null;
        String weather = token.replace("+", "").replace("-", "");
        // Strip off the qualifier if the length == 4
        if (weather.length() == 4) {
            qualifier = StringUtils.substring(weather, 0, 2);
            weather = StringUtils.substring(weather, 2, 4);
        }

        // Strip off any possible modifier, and try to find a match
        PresentWeather presentWeather = null;
        try {
            presentWeather = PresentWeather.valueOf(weather);
        } catch (IllegalArgumentException e) {
            return rval;
        }

        if (null != presentWeather) {
            // Check for a modifier
            presentWeather.setIntensity(PresentWeather.Intensity.MODERATE);

            if (StringUtils.startsWith(token, "+")) {
                presentWeather.setIntensity(PresentWeather.Intensity.HEAVY);
            }

            if (StringUtils.startsWith(token, "-")) {
                presentWeather.setIntensity(PresentWeather.Intensity.LIGHT);
            }

            if (null != qualifier) {
                presentWeather.setQualifier(PresentWeather.Qualifier.valueOf(qualifier));
            }

            metar.presentWeather = presentWeather;

            rval = Boolean.TRUE;
        }

        return rval;
    }

    // CLR
    // SCT070 SCT085 OVC110
    static boolean parseClouds(String token, final Metar metar) {
        boolean rval = Boolean.FALSE;

        // Try to see if the first three characters match a Cloud.Type
        Cloud.Type cloudType = null;
        try {
            cloudType = Cloud.Type.valueOf(StringUtils.substring(token, 0, 3));
        } catch (IllegalArgumentException e) {
            return rval;
        }

        if (null != cloudType) {
            rval = Boolean.TRUE;

            Cloud c = new Cloud();
            c.cloudType = cloudType;

            if (!cloudType.equals(Cloud.Type.CLR)) {
                c.altitude = Integer.parseInt(StringUtils.substring(token, 3, token.length())) * 100;
            }

            metar.clouds.add(c);
        }

        return rval;
    }

    // 14/11  14/M01
    static boolean parseTempDewpoint(String token, final Metar metar) {
        boolean rval = Boolean.FALSE;

        // Is it in the right pattern?
        Pattern dewPoint = Pattern.compile("^(M)?(\\d{2})/(M)?(\\d{2})$");
        Matcher dewPointMatcher = dewPoint.matcher(token);

        if (dewPointMatcher.matches()) {
            rval = Boolean.TRUE;

            metar.temperature = Integer.parseInt(dewPointMatcher.group(2));

            // Is the temperature negative?
            if (null != dewPointMatcher.group(1)) {
                metar.temperature = 0 - metar.temperature;
            }

            metar.dewPoint = Integer.parseInt(dewPointMatcher.group(4));

            // Is the dewpoint negative?
            if (null != dewPointMatcher.group(3)) {
                metar.dewPoint = 0 - metar.dewPoint;
            }
        }

        return rval;
    }

    static boolean parseAltimeter(String token, final Metar metar) {
        boolean rval = Boolean.FALSE;

        if (StringUtils.startsWith(token, "A") && token.length() == 5) {
            rval = Boolean.TRUE;

            Integer alt = Integer.parseInt(StringUtils.substring(token, 1, token.length()));

            metar.altimeter = (float) alt / 100;
        }

        return rval;
    }
}