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();
    }
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 ? ' ' : 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 "".
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 .