waterml/src/main/java/ucar/nc2/ogc/erddap/util/ErddapEDUnits.java
public static String ucumToUdunits(String ucum) {
        if (ucum == null) {
            return null;
        }

        StringBuilder udunits = new StringBuilder();
        int ucLength = ucum.length();
        if (ucLength == 0) {
            return "";
        }

        //is it a time point?  e.g., s{since 1970-01-01T00:00:00T}
        if (ucum.charAt(ucLength - 1) == '}' &&  //quick reject
                ucum.indexOf('}') == ucLength - 1) { //reasonably quick reject
            int sincePo = ucum.indexOf("{since ");
            if (sincePo > 0) {
                //is first part an atomic ucum unit?
                String tUdunits = ucHashMap.get(ucum.substring(0, sincePo));
                if (tUdunits != null) {
                    return tUdunits + " " + ucum.substring(sincePo + 1, ucLength - 1);
                }
            }
        }

        //parse ucum and build udunits, till done        
        int po = 0;  //po is next position to be read
        while (po < ucLength) {
            char ch = ucum.charAt(po);

            //letter  
            if (isUcumLetter(ch)) {     //includes [, ], {, }, 'µ' and "'"
                //find contiguous letters|_|digit (no '-') 
                int po2 = po + 1;
                while (po2 < ucLength &&
                        (isUcumLetter(ucum.charAt(po2)) || ucum.charAt(po2) == '_' ||
                                ErddapString2.isDigit(ucum.charAt(po2)))) {
                    po2++;
                }
                String tUcum = ucum.substring(po, po2);
                po = po2;

                //some ucum have internal digits, but none end in digits 
                //if it ends in digits, treat as exponent
                //find contiguous digits at end
                int firstDigit = tUcum.length();
                while (firstDigit >= 1 && ErddapString2.isDigit(tUcum.charAt(firstDigit - 1))) {
                    firstDigit--;
                }
                String exponent = tUcum.substring(firstDigit);
                tUcum = tUcum.substring(0, firstDigit);
                String tUdunits = oneUcumToUdunits(tUcum);

                //deal with PER -> / 
                if (tUdunits.equals("/")) {
                    char lastUdunits = udunits.length() == 0 ? '\u0000' : udunits.charAt(udunits.length() - 1);
                    if (lastUdunits == '/') {
                        udunits.setCharAt(udunits.length() - 1, '.'); //2 '/' cancel out
                    } else if (lastUdunits == '.') {
                        udunits.setCharAt(udunits.length() - 1, '/'); //  '/' replaces '.'
                    } else {
                        udunits.append('/');
                    }

                } else {
                    udunits.append(tUdunits);
                }

                //add the exponent
                udunits.append(exponent);
                //catch -exponent as a number below

                continue;
            }

            //number
            if (ch == '-' || ErddapString2.isDigit(ch)) {
                //find contiguous digits
                int po2 = po + 1;
                while (po2 < ucLength && ErddapString2.isDigit(ucum.charAt(po2))) {
                    po2++;
                }

                // ^-  or ^{digit}
                if (po2 < ucLength - 1 && Character.toLowerCase(ucum.charAt(po2)) == '^' &&
                        (ucum.charAt(po2 + 1) == '-' || ErddapString2.isDigit(ucum.charAt(po2 + 1)))) {
                    po2 += 2;
                    while (po2 < ucLength && ErddapString2.isDigit(ucum.charAt(po2))) {
                        po2++;
                    }
                }
                String num = ucum.substring(po, po2);
                po = po2;
                udunits.append(num);
                continue;
            }

            // .
            if (ch == '.') {
                po++;
                udunits.append(' '); // ' ' is more common than '.' in udunits
                continue;
            }

            // *
            if (ch == '*') {
                po++;
                udunits.append('^');
                continue;
            }

            // '  ''
            if (ch == '\'') {
                po++;
                if (po < ucLength && ucum.charAt(po) == '\'') {
                    udunits.append("arc_second");
                    po++;
                } else {
                    udunits.append("arc_minute");
                }
                continue;
            }
            //otherwise, punctuation.   copy it
            //  / (division), " doesn't occur,
            udunits.append(ch);
            po++;
        }

        return udunits.toString();
    }
public static String ucumToUdunits(String ucum) {
        if (ucum == null) {
            return null;
        }

        StringBuilder udunits = new StringBuilder();
        int ucLength = ucum.length();
        if (ucLength == 0) {
            return "";
        }

        //is it a time point?  e.g., s{since 1970-01-01T00:00:00T}
        if (ucum.charAt(ucLength - 1) == '}' &&  //quick reject
                ucum.indexOf('}') == ucLength - 1) { //reasonably quick reject
            int sincePo = ucum.indexOf("{since ");
            if (sincePo > 0) {
                //is first part an atomic ucum unit?
                String tUdunits = ucHashMap.get(ucum.substring(0, sincePo));
                if (tUdunits != null) {
                    return tUdunits + " " + ucum.substring(sincePo + 1, ucLength - 1);
                }
            }
        }

        //parse ucum and build udunits, till done        
        int po = 0;  //po is next position to be read
        while (po < ucLength) {
            char ch = ucum.charAt(po);

            //letter  
            if (isUcumLetter(ch)) {     //includes [, ], {, }, 'µ' and "'"
                //find contiguous letters|_|digit (no '-') 
                int po2 = po + 1;
                while (po2 < ucLength &&
                        (isUcumLetter(ucum.charAt(po2)) || ucum.charAt(po2) == '_' ||
                                ErddapString2.isDigit(ucum.charAt(po2)))) {
                    po2++;
                }
                String tUcum = ucum.substring(po, po2);
                po = po2;

                //some ucum have internal digits, but none end in digits 
                //if it ends in digits, treat as exponent
                //find contiguous digits at end
                int firstDigit = tUcum.length();
                while (firstDigit >= 1 && ErddapString2.isDigit(tUcum.charAt(firstDigit - 1))) {
                    firstDigit--;
                }
                String exponent = tUcum.substring(firstDigit);
                tUcum = tUcum.substring(0, firstDigit);
                String tUdunits = oneUcumToUdunits(tUcum);

                //deal with PER -> / 
                if (tUdunits.equals("/")) {
                    char lastUdunits = udunits.length() == 0 ? '\u0000' : udunits.charAt(udunits.length() - 1);
                    if (lastUdunits == '/') {
                        udunits.setCharAt(udunits.length() - 1, '.'); //2 '/' cancel out
                    } else if (lastUdunits == '.') {
                        udunits.setCharAt(udunits.length() - 1, '/'); //  '/' replaces '.'
                    } else {
                        udunits.append('/');
                    }

                } else {
                    udunits.append(tUdunits);
                }

                //add the exponent
                udunits.append(exponent);
                //catch -exponent as a number below

                continue;
            }

            //number
            if (ch == '-' || ErddapString2.isDigit(ch)) {
                //find contiguous digits
                int po2 = po + 1;
                while (po2 < ucLength && ErddapString2.isDigit(ucum.charAt(po2))) {
                    po2++;
                }

                // ^-  or ^{digit}
                if (po2 < ucLength - 1 && Character.toLowerCase(ucum.charAt(po2)) == '^' &&
                        (ucum.charAt(po2 + 1) == '-' || ErddapString2.isDigit(ucum.charAt(po2 + 1)))) {
                    po2 += 2;
                    while (po2 < ucLength && ErddapString2.isDigit(ucum.charAt(po2))) {
                        po2++;
                    }
                }
                String num = ucum.substring(po, po2);
                po = po2;
                udunits.append(num);
                continue;
            }

            // .
            if (ch == '.') {
                po++;
                udunits.append(' '); // ' ' is more common than '.' in udunits
                continue;
            }

            // *
            if (ch == '*') {
                po++;
                udunits.append('^');
                continue;
            }

            // '  ''
            if (ch == '\'') {
                po++;
                if (po < ucLength && ucum.charAt(po) == '\'') {
                    udunits.append("arc_second");
                    po++;
                } else {
                    udunits.append("arc_minute");
                }
                continue;
            }
            //otherwise, punctuation.   copy it
            //  / (division), " doesn't occur,
            udunits.append(ch);
            po++;
        }

        return udunits.toString();
    }
This converts UCUM to UDUnits.
<br>UDUnits: http://www.unidata.ucar.edu/software/udunits/udunits-1/etc/udunits.dat
http://www.unidata.ucar.edu/software/udunits/udunits-2/udunits2.html
<br>UCUM: http://unitsofmeasure.org/ucum.html
<p/>
<p>UCUM tends to be short, canonical-only, and strict.
Many UCUM units are the same in UDUnits.
<p/>
<p>UDUnits supports lots of aliases (short and long)
and plurals (usually by adding 's').
This tries to convert UCUM to a short, common UDUNIT units.
<p/>
<p>Problems:
<ul>
<li> UCUM has only "deg", no concept of degree_east|north|true|true.
</ul>
<p/>
<p>Notes:
<ul>
<li>This method is a strictly case sensitive.
<li>For "10 to the", UCUM allows 10* or 10^. This method uses 10^.
<li>{ntu} becomes NTU.
<li>{psu} becomes PSU.
</ul>
<p/>
return the UCUM converted to UDUNITS.
null returns null. "" returns "".