/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.units;

import java.io.Serializable;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * Provides most of a concrete implementation of a database of units.
 *
 * @author Steven R. Emmerson
 */
public class UnitDBImpl implements UnitDB, Serializable {
	private static final long		serialVersionUID	= 1L;

	/**
	 * The set of units.
	 *
	 * @serial
	 */
	private final Set<Unit>			unitSet;

	/**
	 * The name-to-unit map.
	 *
	 * @serial
	 */
	private final Map<String, Unit>	nameMap;

	/**
	 * The symbol-to-unit map.
	 *
	 * @serial
	 */
	private final Map<String, Unit>	symbolMap;

	/**
	 * Constructs from the expected number of names and symbols. The sizes will
	 * be used to construct the initial database but will not limit its growth.
	 *
	 * @param nameCount
	 *            The expected number of names (including plurals and aliases).
	 * @param symbolCount
	 *            The expected number of symbols.
	 */
	protected UnitDBImpl(final int nameCount, final int symbolCount) {
		unitSet = new TreeSet<Unit>(new Comparator<Unit>() {
			public int compare(final Unit obj1, final Unit obj2) {
				return (obj1).getName().compareTo((obj2).getName());
			}
		});
		nameMap = new Hashtable<String, Unit>(nameCount + 1);
		symbolMap = new Hashtable<String, Unit>(symbolCount + 1);
	}

	/**
	 * Adds all the entries in another UnitDBImpl to this database.
	 *
	 * @param that
	 *            The other UnitDBImpl.
	 * @throws UnitExistsException
	 *             Attempt to redefine an existing entry.
	 */
	public void add(final UnitDBImpl that) throws UnitExistsException {
		unitSet.addAll(that.unitSet);
		nameMap.putAll(that.nameMap);
		symbolMap.putAll(that.symbolMap);
	}

	/**
	 * Return the number of names in this database
	 *
	 * @return The total number of names, plurals, and aliases.
	 */
	public int nameCount() {
		return nameMap.size();
	}

	/**
	 * Return the number of symbols in this database.
	 *
	 * @return The number of symbols in this database.
	 */
	public int symbolCount() {
		return symbolMap.size();
	}

	/**
	 * Adds a unit to the database.
	 *
	 * @param unit
	 *            The unit to be added.
	 * @throws UnitExistsException
	 *             Another unit with the same name or symbol already exists in
	 *             the database.
	 * @throws NameException
	 *             Bad unit name.
	 */
	public void addUnit(final Unit unit) throws UnitExistsException,
			NameException {
		if (unit.getName() == null) {
			throw new NameException("Unit name can't be null");
		}
		addByName(unit.getName(), unit);
		addByName(unit.getPlural(), unit);
		addBySymbol(unit.getSymbol(), unit);
		unitSet.add(unit);
	}

	/**
	 * Adds an alias for a unit already in the database.
	 *
	 * @param alias
	 *            An alias for the unit.
	 * @param name
	 *            The name of the unit already in the database.
	 * @throws UnitExistsException
	 *             Another unit with the same name or symbol already exists in
	 *             the database.
	 * @throws NoSuchUnitException
	 *             The unit isn't in the database.
	 */
	public final void addAlias(final String alias, final String name)
			throws NoSuchUnitException, UnitExistsException {
		addAlias(alias, name, null);
	}

	/**
	 * Adds an alias for a unit already in the database.
	 *
	 * @param alias
	 *            An alias for the unit.
	 * @param name
	 *            The name of the unit already in the database.
	 * @param symbol
	 *            The symbol for the unit.
	 * @throws UnitExistsException
	 *             Another unit with the same name or symbol already exists in
	 *             the database.
	 * @throws NoSuchUnitException
	 *             The unit isn't in the database.
	 */
	public final void addAlias(final String alias, final String name,
			final String symbol) throws NoSuchUnitException,
			UnitExistsException {
		addAlias(alias, name, symbol, null);
	}

	/**
	 * Adds a symbol for a unit already in the database.
	 *
	 * @param symbol
	 *            The symbol for the unit.
	 * @param name
	 *            The name of the unit already in the database.
	 * @throws UnitExistsException
	 *             Another unit with the same name or symbol already exists in
	 *             the database.
	 * @throws NoSuchUnitException
	 *             The unit isn't in the database.
	 */
	public final void addSymbol(final String symbol, final String name)
			throws NoSuchUnitException, UnitExistsException {
		addAlias(null, name, symbol, null);
	}

	/**
	 * Adds an alias for a unit already in the database.
	 *
	 * @param alias
	 *            The alias to be added to the database. May be null.
	 * @param name
	 *            The name of the unit to have an alias added to the database.
	 * @param symbol
	 *            The symbol to be added. May be null.
	 * @param plural
	 *            The plural form of the alias. If null
     *				, then regular
	 *            plural-forming rules are followed.
	 * @throws NoSuchUnitException
	 *             The unit is not in the database.
	 * @throws UnitExistsException
	 *             Another unit with the same alias is already in the database.
	 */
	public final void addAlias(final String alias, final String name,
			final String symbol, final String plural)
			throws NoSuchUnitException, UnitExistsException {
		addAlias(UnitID.newUnitID(alias, plural, symbol), name);
	}

	/**
	 * Adds an alias for a unit already in the database.
	 *
	 * @param alias
	 *            The alias to be added to the database.
	 * @param name
	 *            The name of the unit to have an alias added to the database.
	 * @throws NoSuchUnitException
	 *             The unit is not in the database.
	 * @throws UnitExistsException
	 *             Another unit with the same alias is already in the database.
	 */
	public final void addAlias(final UnitID alias, final String name)
			throws NoSuchUnitException, UnitExistsException {
		final Unit unit = getByName(name);
		if (unit == null) {
			throw new NoSuchUnitException(name);
		}
		addByName(alias.getName(), unit);
		addByName(alias.getPlural(), unit);
		addBySymbol(alias.getSymbol(), unit);
	}

	/**
	 * Gets a unit by either name, plural, or symbol. Retrieving the unit by
	 * symbol is attempted before retrieving the unit by name because symbol
	 * comparisons are case sensitive and, hence, should be more robust.
	 *
	 * @param id
	 *            The id to be matched.
	 * @return The unit whose name, plural, or symbol matches or
	 *         null if no such unit was found.
	 */
	public Unit get(final String id) {
		Unit unit = getBySymbol(id);
		if (unit == null) {
			unit = getByName(id);
		}
		return unit;
	}

	/**
	 * Gets a unit by name.
	 *
	 * @param name
	 *            The name to be matched.
	 * @return The unit whose name, plural, or alias matches or
	 *         null if no such unit was found.
	 */
	public Unit getByName(final String name) {
		return nameMap.get(canonicalize(name));
	}

	/**
	 * Returns the canonical form of a unit name.
	 *
	 * @param name
	 *            A unit name.
	 * @return The canonical form of the name.
	 */
	private static String canonicalize(final String name) {
		return name.toLowerCase().replace(' ', '_');
	}

	/**
	 * Gets a unit by symbol.
	 *
	 * @param symbol
	 *            The symbol to be matched.
	 * @return The unit whose symbol matches or null
     *				 if no such unit was
	 *         found.
	 */
	public Unit getBySymbol(final String symbol) {
		return symbolMap.get(symbol);
	}

	/**
	 * Returns the string representation of this database.
	 *
	 * @return The string representation of this database.
	 */
	@Override
	public String toString() {
		return unitSet.toString();
	}

	/**
	 * Gets an iterator over the units in the database.
	 *
	 * @return An iterator over the units in the database. The iterator's
	 *         next() method returns objects of type
	 *         Unit.
	 */
	@SuppressWarnings("unchecked")
	public final Iterator getIterator() {
		return unitSet.iterator();
	}

	/**
	 * Adds a unit to the database by name.
	 *
	 * @param name
	 *            The name of the unit. If null then the unit is
	 *            not added.
	 * @param newUnit
	 *            The unit to be added.
	 * @throws UnitExistsException
	 *             Attempt to redefine an existing unit.
	 */
	private final void addByName(final String name, final Unit newUnit)
			throws UnitExistsException {
		if (name != null) {
			addUnique(nameMap, canonicalize(name), newUnit);
		}
	}

	/**
	 * Adds a unit to the database by symbol.
	 *
	 * @param symbol
	 *            The symbol for the unit. If null then the unit is
	 *            not added.
	 * @param newUnit
	 *            The unit to be added.
	 * @throws UnitExistsException
	 *             Attempt to redefine an existing unit.
	 */
	private final void addBySymbol(final String symbol, final Unit newUnit)
			throws UnitExistsException {
		if (symbol != null) {
			addUnique(symbolMap, symbol, newUnit);
		}
	}

	/**
	 * Adds a unique unit to a map..
	 *
	 * @param map
	 *            The map to be added to.
	 * @param key
	 *            The key for the unit entry.
	 * @param newUnit
	 *            The unit to be added.
	 * @throws UnitExistsException
	 *             Attempt to redefine an existing unit.
	 */
	private static final void addUnique(final Map<String, Unit> map,
			final String key, final Unit newUnit) throws UnitExistsException {
		final Unit oldUnit = map.put(key, newUnit);
		if (oldUnit != null && !oldUnit.equals(newUnit)) {
			throw new UnitExistsException(oldUnit, newUnit);
		}
	}
}


/* Copyright 2009, UCAR/Unidata and OPeNDAP, Inc.
   See the LICENCE file for more information. */

package dap4.d4ts;

import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import dap4.dap4lib.DapLog;
import dap4.servlet.DapRequest;

import java.io.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Given a directory, return a front page of HTML
 * that lists all of the files in that page.
 *
 * @author Dennis Heimbigner
 */

public class FrontPage
{

    static final boolean DUMPFILELIST = false;

    //////////////////////////////////////////////////
    // Constants

    static final protected boolean NO_VLEN = false; // ignore vlen datasets for now

    static final protected String[] expatterns = new String[0];

    // Define the file sources of interest
    static final protected FileSource[] SOURCES = new FileSource[]{
            new FileSource(".nc", "netCDF"),
            new FileSource(".hdf5", "HDF5"),
            new FileSource(".dap", "Raw Protocol Output"),
            new FileSource(".syn", "Synthetic")
    };

    static public class Root
    {
        public String prefix;
        public String dir;
        public List<FileSource> files;

        public String toString()
        {
            return String.format("{'%s/%s'}", this.prefix,this.dir);
        }

        public Root(String dir, String prefix)
        {
            this.dir = dir;
            this.prefix = DapUtil.canonicalpath(prefix);
        }

        public String getFullPath() {return DapUtil.canonjoin(this.prefix,this.dir);}

        public void setFiles(List<FileSource> files)
        {
            this.files = files;
        }
    }

    // Remote Test server: should match values in TestDir.java
    private static String dap4TestServerPropName = "d4ts";
    static public String dap4TestServer = "remotetest.unidata.ucar.edu"; //mutable

    static {
        String d4ts = System.getProperty(dap4TestServerPropName);
        if(d4ts != null && d4ts.length() > 0)
            dap4TestServer = d4ts;
    }

    //////////////////////////////////////////////////

    static class FileSource
    {
        public String ext = null;
        public String tag = null;
        public List<File> files = null;

        public FileSource(String ext, String tag)
        {
            this.ext = ext;
            this.tag = tag;
        }
    }

    //////////////////////////////////////////////////
    // Instance Variables

    protected DapRequest drq = null;
    protected List<Root> roots = null; // root paths to the displayed files

    //////////////////////////////////////////////////
    // Constructor(s)

    /**
     * @param rootinfo the file directory roots
     * @throws DapException
     */
    public FrontPage(List<Root> rootinfo, DapRequest req)
            throws DapException
    {
        this.drq = req;
        this.roots = rootinfo;
        for(Root root : this.roots) {
            // Construct the list of usable files
            buildFileList(root);
        }

    }
    //////////////////////////////////////////////////

    protected void
    buildFileList(Root rootinfo)
            throws DapException
    {
        File root = new File(rootinfo.getFullPath());
        if(!root.isDirectory())
            throw new DapException("FrontPage: specified root directory is not a directory: " + rootinfo.getFullPath());
        if(!root.canRead())
            throw new DapException("FrontPage: specified root directory is not readable: " + rootinfo.getFullPath());

        // take files from set of files immediately under root
        File[] candidates = root.listFiles();
        List<FileSource> activesources = new ArrayList<FileSource>();
        // Capture lists of files for each FileSource
        for(FileSource src : SOURCES) {
            List<File> matches = new ArrayList<File>();
            for(File candidate : candidates) {
                String name = candidate.getName();
                boolean excluded = false;
                for(String exclude : expatterns) {
                    if(name.indexOf(exclude) >= 0) {
                        excluded = true;
                        break;
                    }
                }
                if(excluded) continue;
                if(!name.endsWith(src.ext)) continue;
                if(!candidate.canRead()) {
                    DapLog.info("FrontPage: file not readable: " + candidate);
                    continue;
                }
                matches.add(candidate);
            }
            if(matches.size() > 0) {
                // Sort the set of files
                matches.sort(new Comparator<File>() {
                    public int compare(File f1, File f2) {
                         return f1.getName().compareTo(f2.getName());
                    }});
                if(DUMPFILELIST) {
                    for(File x : matches) {
                        System.err.printf("file: %s/%s%n", rootinfo.prefix, x.getName());
                    }
                }
                FileSource clone = new FileSource(src.ext, src.tag);
                clone.files = matches;
                activesources.add(clone);
            }
        }
        rootinfo.setFiles(activesources);
    }

    protected String
    buildPage()
            throws DapException
    {
        StringBuilder html = new StringBuilder();
        html.append(HTML_PREFIX);
        html.append(HTML_HEADER1);

        StringBuilder rootnames = new StringBuilder();
        for(Root root : this.roots) {
            if(rootnames.length() > 0)
                rootnames.append(",");
           rootnames.append(root.dir);
        }
        html.append(String.format(HTML_HEADER2,rootnames));

        for(Root root : this.roots) {
            try {
                for(FileSource src : root.files) {
                    html.append(String.format(HTML_HEADER3, src.tag));
                    html.append(TABLE_HEADER);
                    for(File file : src.files) {
                        String name = file.getName();
                        StringBuilder buf = new StringBuilder();
                        buf.append(this.drq.getControllerPath());
                        buf.append('/');
                        buf.append(DapUtil.canonicalpath(root.dir));
                        buf.append('/');
                        buf.append(DapUtil.canonicalpath(name));
                        String urlpath = buf.toString();
                        String line = String.format(HTML_FORMAT,
                                name,
                                urlpath,
                                urlpath,
                                urlpath
                        );
                        html.append(line);
                    }
                    html.append(TABLE_FOOTER);
                }
            } catch (Exception e) {
                sendtrace(drq, e);
            }
        }
        html.append(HTML_FOOTER);
        return html.toString();
    }

    static public void
    sendtrace(DapRequest drq, Exception e)
    {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            pw.close();
            sw.close();
            sendtext(drq, sw.toString());
        } catch (IOException ioe) {
            assert false;
        }
    }

    static public void
    sendtext(DapRequest drq, String s)
    {
        try {
            //s = s.replace("<", "<");
            //s = s.replace(">", ">");
            //s = s + "\n";
            s = "+++++++++\n" + s;
            s = s + "+++++++++\n";
            byte[] bytes = DapUtil.extract(DapUtil.UTF8.encode(s));
            OutputStream out = drq.getOutputStream();
            out.write(bytes);
            out.flush();
        } catch (IOException ioe) {
            assert false;
        }
    }

    /**
     * Get a flattened list of all files immediately
     * under a given root with given extension(s).
     */

    /*static List<File>
    collectFiles(File dir)
    {
        List<File> result = new ArrayList<File>();
        if(!dir.isDirectory())
                DapLog.info("FrontPage: specified root is not a directory: " + dir);
        if(!dir.canRead())
            DapLog.info("FrontPage: specified root directory is not readable: " + dir);

        File[] contents = dir.listFiles();
        Arrays.sort(contents);
        for(File f : contents) {
            if(f.isDirectory()) {
                List<File> subfiles = walkDir(f);
                result.addAll(subfiles);
            } else {
                result.add(f);
            }
        }
        return result;
    }*/

    //////////////////////////////////////////////////
    // HTML prefix and suffix
    // (Remember that java does not allow Strings to cross lines)
    static final String HTML_PREFIX =
            "\n\n<title>DAP4 Test Files</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html\">\n</meta>\n\n";

    static final String HTML_HEADER1 = "
DAP4 Test Files
\n";
    static final String HTML_HEADER2 = "
http://" + dap4TestServer + "/d4ts/{%s}
%n
%n";
    static final String HTML_HEADER3 = "
%s Based Test Files
%n";

    static final String TABLE_HEADER = "
\n";
    static final String TABLE_FOOTER = "
\n";

    static final String HTML_FOOTER = "
\n\n";

    static final String HTML_FORMAT =
            "
%n"
                    + "%s:	%n"
                    + " DMR.XML
	%n"
                    + " DAP
	%n"
                    + " DSR.XML
	%n"
                    + "
%n";
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ui.widget;

import java.awt.event.ActionEvent;
import java.lang.invoke.MethodHandles;
import javax.swing.Action;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 * Pluggable Look and Feel management.
 *
 * @author John Caron
 */

public class PLAF {
    private final static org.slf4j.Logger logger
                            = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private JComponent jc;
    private boolean debug = false;

/*  Constructor.
 *
 *  @param tree the top-level JComponent tree: everything in this tree will get switched to
 *  the new L&F. Everything not in the tree (eg Dialogs) should listen for changes like:
 *  <pre>
         UIManager.addPropertyChangeListener( new PropertyChangeListener() {
          public void propertyChange( PropertyChangeEvent e) {
            if (e.getPropertyName().equals("lookAndFeel"))
              SwingUtilities.updateComponentTreeUI( <myDialogObject>);
          }
        });
    </pre>
*/
    public PLAF(JComponent jc) {
        this.jc = jc;
    }

/**
 * Add a set of MenuItems to the given JMenu, one for each possible L&F.
 * if this platform doesnt support the L&F, disable the MenuItem.
 */
    public void addToMenu(final JMenu menu) {
        final UIManager.LookAndFeelInfo[] plafInfo = UIManager.getInstalledLookAndFeels();
        for (UIManager.LookAndFeelInfo aPlafInfo : plafInfo) {
            addToMenu(aPlafInfo.getName(), aPlafInfo.getClassName(), menu);
        }

        final LookAndFeel current = UIManager.getLookAndFeel();
        System.out.printf("current L&F=%s%n", current.getName());
    }

/**
 *
 */
    private void addToMenu(final String name, final String className, final JMenu menu) {
        logger.debug("PLAF LookAndFeelInfo  {}", className);
        boolean isSupported = true;
        try {
            final Class cl = Class.forName(className);
            final LookAndFeel lf = (LookAndFeel) cl.newInstance();
            if (!lf.isSupportedLookAndFeel()) {
                isSupported = false;
            }
        }
        catch (Throwable t) {
            isSupported = false;
        }

        final AbstractAction act = new PLAFAction(name, className);
        final JMenuItem mi = menu.add(act);
        if (!isSupported) {
            mi.setEnabled(false);
        }
    }

/**
 *
 */
    private class PLAFAction extends AbstractAction {
        final String plafClassName;

        PLAFAction(final String name, final String plafClassName) {
            this.plafClassName = plafClassName;
            putValue(Action.NAME, name);
        }

    /** */
        @Override
        public void actionPerformed(final ActionEvent evt) {
            try {
                UIManager.setLookAndFeel(plafClassName);
            }
            catch (Exception ex) {
                ex.printStackTrace();
                return;
            }

            // This sets L&F for top level and its children only
            // Dialog boxes must listen fo L&F PropertyChangeEvents

            final JFrame parentFrame = (JFrame)jc.getTopLevelAncestor ( );

            SwingUtilities.updateComponentTreeUI(parentFrame);
        }
    }
}


/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2010, OPeNDAP, Inc.
// Copyright (c) 2002,2003 OPeNDAP, Inc.
//
// Author: James Gallagher <jgallagher@opendap.org>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms,
// with or without modification, are permitted provided
// that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright
//   notice, this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
//
// - Neither the name of the OPeNDAP nor the names of its contributors may
//   be used to endorse or promote products derived from this software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/////////////////////////////////////////////////////////////////////////////

package opendap.util;

import java.util.Enumeration;

import opendap.dap.*;

/**
 */
public class dasTools {

    /**
     * This code could use a real `kill-file' some day -
     * about the same time that the rest of the server gets
     * an `rc' file... For the present just return
     * false (There is no killing going on here...)
     *

     * The C++ implementation looks like this:
     *

     * static bool
     * name_in_kill_file(const string &name)
     * {
     * static Regex dim(".*_dim_[0-9]*", 1); // HDF `dimension' attributes.
     *

     * return dim.match(name.c_str(), name.length()) != -1;
     * }
     */
    public static boolean nameInKillFile(String name) {
        return (false);
    }

    public static boolean nameInDDS(String name, DDS dds) {

        boolean found = true;

        try {
            dds.getVariable(name);
        }
        catch (NoSuchVariableException e) {

            found = false;
        }

        //System.out.println("nameInDDS(): "+found);
        return (found);
    }

    /* C++ implementation
    static bool
    name_is_global(string &name)
    {
        static Regex global("\\(.*global.*\\)\\|\\(.*opendap.*\\)", 1);
        downcase(name);
        return global.match(name.c_str(), name.length()) != -1;
    }
    */
    public static boolean nameIsGlobal(String name) {

        String lcName = name.toLowerCase();
        boolean global = false;

        if (lcName.indexOf("global") >= 0)
            global = true;

        if (lcName.indexOf("dods") >= 0)
            global = true;

        //System.out.println("nameIsGlobal(): "+global);

        return (global);
    }

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    public static String fancyTypeName(BaseType bt) {

        if (bt instanceof DByte)
            return ("8 bit Byte");

        if (bt instanceof DUInt16)
            return ("16 bit Unsigned Integer");

        if (bt instanceof DInt16)
            return ("16 bit Integer");

        if (bt instanceof DUInt32)
            return ("32 bit Unsigned Integer");

        if (bt instanceof DInt32)
            return ("32 bit Integer");

        if (bt instanceof DFloat32)
            return ("32 bit Real");

        if (bt instanceof DFloat64)
            return ("64 bit Real");

        if (bt instanceof DURL)
            return ("URL");

        if (bt instanceof DString)
            return ("String");

        if (bt instanceof DArray) {

            DArray a = (DArray) bt;
            StringBuilder type = new StringBuilder();
            type.append("Array of ");
            type.append(fancyTypeName(a.getPrimitiveVector().getTemplate()));
            type.append("s ");

            Enumeration e = a.getDimensions();
            while (e.hasMoreElements()) {
                DArrayDimension dad = (DArrayDimension) e.nextElement();
                type.append("[");
                type.append(dad.getEncodedName());
                type.append(" = 0..");
                type.append(dad.getSize() - 1);
                type.append("]");

            }
            type.append("\n");
            return (type.toString());
        }

        if (bt instanceof DStructure)
            return ("Structure");

        if (bt instanceof DSequence)
            return ("Sequence");

        if (bt instanceof DGrid)
            return ("Grid");

        return ("UNKNOWN");

    }
    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.grib.grib2;

import javax.annotation.Nullable;
import ucar.nc2.grib.GribNumbers;
import ucar.unidata.io.KMPMatch;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.StringUtil2;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Scan raf for grib-2 messages
 *
 * @author caron
 * @since 3/28/11
 */
public class Grib2RecordScanner {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Grib2RecordScanner.class);
  private static final KMPMatch matcher = new KMPMatch(new byte[] {'G','R','I','B'} );
  private static final boolean debug = false;
  private static final boolean debugRepeat = false;
  private static final boolean debugEnding = false;
  private static final int maxScan = 16000;

  public static boolean isValidFile(RandomAccessFile raf) {
    try {
      raf.seek(0);
      boolean found = raf.searchForward(matcher, maxScan); // look in first 16K
      if (!found) return false;
      raf.skipBytes(7); // will be positioned on byte 0 of indicator section
      int edition = raf.read(); // read at byte 8
      if (edition != 2) return false;

      // check ending = 7777
      long len = GribNumbers.int8(raf);
      if (len > raf.length()) return false;
      raf.skipBytes(len-20);
      for (int i = 0; i < 4; i++) {
        if (raf.read() != 55) return false;
      }
      return true;

    } catch (IOException e) {
      return false;
    }
  }

  /**
   * tricky bit of business. recapture the entire record based on drs position.
   * for validation.
   * @param raf             from this RandomAccessFile
   * @param drsPos          Grib2SectionDataRepresentation starts here
   */
  @Nullable
  public static Grib2Record findRecordByDrspos(RandomAccessFile raf, long drsPos) throws IOException {
    long pos = Math.max(0, drsPos- (20*1000)); // go back 20K
    Grib2RecordScanner scan = new Grib2RecordScanner(raf, pos);
    while (scan.hasNext()) {
      ucar.nc2.grib.grib2.Grib2Record gr = scan.next();
      Grib2SectionDataRepresentation drs = gr.getDataRepresentationSection();
      if (drsPos == drs.getStartingPosition()) return gr;
      if (raf.getFilePointer() > drsPos) break;   // missed it.
    }
    return null;
  }

  //////////////////////////////////////////////////////////////////////////////

  private final Map<Long, Grib2SectionGridDefinition> gdsMap = new HashMap<>();
  private final ucar.unidata.io.RandomAccessFile raf;

  private byte[] header;
  private int badEndings = 0;
  private long lastPos;    // start scanning from here

  // deal with repeating sections - each becomes a Grib2Record
  private long repeatPos = -1;             // if > 0, we are in middle of repeating record
  private Grib2Record repeatRecord = null; // current repeating record
  private Grib2SectionBitMap repeatBms = null; // current repeating bms

  public Grib2RecordScanner(RandomAccessFile raf) throws IOException {
    this.raf = raf;
    raf.seek(0);
    raf.order(RandomAccessFile.BIG_ENDIAN);
    lastPos = 0;

    if (debugRepeat) logger.debug(" Grib2RecordScanner %s%n", raf.getLocation());
  }

  private Grib2RecordScanner(RandomAccessFile raf, long startFrom) throws IOException {
    this.raf = raf;
    raf.seek(startFrom);
    raf.order(RandomAccessFile.BIG_ENDIAN);
    lastPos = startFrom;
  }

  public boolean hasNext() throws IOException {
    if (lastPos >= raf.length()) return false;
    if (repeatPos > 0) {
      if (nextRepeating()) // this has created a new repeatRecord
        return true;
    } else {
      repeatRecord = null;
      repeatBms = null;
      // fall through to new record
    }

    boolean more;
    long gribStart = 0;

    while (true) { // scan until we get a GRIB-2 or more == false
      raf.seek(lastPos);
      more = raf.searchForward(matcher, -1); // will scan to end for a 'GRIB' string
      if (!more) break;

      gribStart = raf.getFilePointer();           // this is where the next 'GRIB' starts
      // see if its GRIB-2
      raf.skipBytes(7);
      int edition = raf.read();
      if (edition == 2) break;
      lastPos = raf.getFilePointer();   // not edition 2 ! just skip it !! start scanning from there
      logger.warn("GRIB message at pos=" + gribStart + " not GRIB2; skip");
    }

    if (more) {
      int sizeHeader = (int) (gribStart - lastPos);  // wmo headers are embedded between records in some idd streams
      if (debugEnding) logger.debug("bytes between last and next=%d%n", sizeHeader);
      if (sizeHeader > 100) sizeHeader = 100;   // maximum 100 bytes; more is likely to be garbage
      long goBack = gribStart-sizeHeader;
      header = new byte[sizeHeader];
      raf.seek(goBack);
      raf.readFully(header);
      raf.seek(gribStart);
      this.lastPos = gribStart; // ok start from here next time
    }

    return more;
  }

  public Grib2Record next() throws IOException {
    if (repeatRecord != null) { // serve current repeatRecord if it exists
      return new Grib2Record(repeatRecord);
    }

    Grib2SectionIndicator is = null;
    try {
      is = new Grib2SectionIndicator(raf);
      Grib2SectionIdentification ids = new Grib2SectionIdentification(raf);
      Grib2SectionLocalUse lus = new Grib2SectionLocalUse(raf);
      Grib2SectionGridDefinition gds = new Grib2SectionGridDefinition(raf);
      Grib2SectionProductDefinition pds = new Grib2SectionProductDefinition(raf);
      Grib2SectionDataRepresentation drs = new Grib2SectionDataRepresentation(raf);
      Grib2SectionBitMap bms = new Grib2SectionBitMap(raf);
      Grib2SectionData dataSection = new Grib2SectionData(raf);
      if (dataSection.getMsgLength() > is.getMessageLength()) { // presumably corrupt
        throw new IllegalStateException("Illegal Grib2SectionData Message Length");
      }

      // look for duplicate gds
      long crc = gds.calcCRC();
      Grib2SectionGridDefinition gdsCached = gdsMap.get(crc);
      if (gdsCached != null)
        gds = gdsCached;       // hmmmm why ??
      else
        gdsMap.put(crc, gds);

      // check to see if we have a repeating record
      long pos = raf.getFilePointer();
      long ending = is.getEndPos();
      if (pos+34 < ending) { // give it 30 bytes of slop
        if (debugRepeat) logger.debug(" REPEAT AT %d != %d%n", pos+4, ending);
        repeatPos = pos;
        repeatRecord = new Grib2Record(header, is, ids, lus, gds, pds, drs, bms, dataSection, false, Grib2Index.ScanModeMissing); // this assumes immutable sections
        // track bms in case its a repeat
        if (bms.getBitMapIndicator() == 0)
          repeatBms = bms;
        return new Grib2Record(repeatRecord); // GribRecord isnt immutable; still, may not be necessary
      }

      if (debug) logger.debug(" read until %d grib ending at %d header ='%s'%n", raf.getFilePointer(), ending, StringUtil2.cleanup(header));

      // check that end section is correct
      boolean foundEnding = true;
      raf.seek(ending-4);
      for (int i = 0; i < 4; i++) {
        if (raf.read() != 55) {
          foundEnding = false;
          badEndings++;
          //String clean = StringUtil2.cleanup(header);
          //if (clean.length() > 40) clean = clean.substring(0,40) + "...";
          if (debug) logger.debug(" **missing End of GRIB message at pos=%d start= %d%n", ending, is.getStartPos());
          logger
              .warn("Missing End of GRIB message {} starting at pos={} is.ending={} data.ending={} file={}", badEndings, is.getStartPos(),ending,dataSection.getEndingPosition(),raf.getLocation());
          break;
        }
      }
      if (debug) logger.debug(" read until %d grib ending at %d header ='%s' foundEnding=%s%n",
              raf.getFilePointer(), ending, StringUtil2.cleanup(header), foundEnding);

      if (foundEnding || debugEnding) {
        lastPos = raf.getFilePointer();
        return new Grib2Record(header, is, ids, lus, gds, pds, drs, bms, dataSection, false, Grib2Index.ScanModeMissing);

      } else { // skip this record
        // lastPos = is.getEndPos() + 20;   dont trust is.getEndPos()
        lastPos += 20;  // skip "GRIB"
        if (hasNext()) // search forward for another one
         return next();
      }

    } catch (Throwable t) {
      long pos = (is == null) ? -1 : is.getStartPos();
      logger.warn("Bad GRIB2 record in file {}, skipping pos={} cause={}", raf.getLocation(), pos, t.getMessage());
      lastPos += 20;  // skip "GRIB"
      if (hasNext()) // search forward for another one
        return next();
    }

    throw new IOException("last record was incomplete");
  }

  // return true if got another repeat out of this record
  // side effect is that the new record is in repeatRecord
  private boolean nextRepeating() throws IOException {
    raf.seek(repeatPos);

    GribNumbers.int4(raf); // skip octets 1-4
    int section = raf.read(); // find out what section this is
    raf.seek(repeatPos); // back to beginning of section

    if (section == 2) {
      repeatRecord.setLus(new Grib2SectionLocalUse(raf));
      repeatRecord.setGdss(new Grib2SectionGridDefinition(raf));
      repeatRecord.setPdss(new Grib2SectionProductDefinition(raf));
      repeatRecord.setDrs(new Grib2SectionDataRepresentation(raf));
      repeatRecord.setBms(new Grib2SectionBitMap(raf), false);
      repeatRecord.setDataSection(new Grib2SectionData(raf));
      repeatRecord.repeat = section;

    } else if (section == 3) {
      repeatRecord.setGdss(new Grib2SectionGridDefinition(raf));
      repeatRecord.setPdss(new Grib2SectionProductDefinition(raf));
      repeatRecord.setDrs(new Grib2SectionDataRepresentation(raf));
      repeatRecord.setBms(new Grib2SectionBitMap(raf), false);
      repeatRecord.setDataSection(new Grib2SectionData(raf));
      repeatRecord.repeat = section;

    } else if (section == 4) {
      repeatRecord.setPdss(new Grib2SectionProductDefinition(raf));
      repeatRecord.setDrs(new Grib2SectionDataRepresentation(raf));
      repeatRecord.setBms(new Grib2SectionBitMap(raf), false);
      repeatRecord.setDataSection(new Grib2SectionData(raf));
      repeatRecord.repeat = section;

    } else {
      if (debugRepeat) logger.debug(" REPEAT Terminate %d%n", section);
      lastPos = repeatPos; // start next scan from here
      repeatPos = -1;
      repeatRecord = null;
      repeatBms = null;
      return false;
    }

    // look for repeating bms
    Grib2SectionBitMap bms = repeatRecord.getBitmapSection();
    if (bms.getBitMapIndicator() == 254) {
      // replace BMS with last good one
      if (repeatBms == null)
        throw new IllegalStateException("No bms in repeating section");
      repeatRecord.setBms(repeatBms, true);
      //debug
      if (debugRepeat) logger.debug("replaced bms %d%n", section);
      repeatRecord.repeat += 1000;

    } else if (bms.getBitMapIndicator() == 0) {
      // track last good bms
      repeatBms = repeatRecord.getBitmapSection();
    }

    // keep only unique gds
    if ((section == 2) || (section == 3)) {
      // look for duplicate gds
      Grib2SectionGridDefinition gds = repeatRecord.getGDSsection();
      long crc = gds.calcCRC();
      Grib2SectionGridDefinition gdsCached = gdsMap.get(crc);
      if (gdsCached != null)
        repeatRecord.setGdss(gdsCached);
      else
        gdsMap.put(crc, gds);
    }

   // check to see if we are at the end
    long pos = raf.getFilePointer();
    long ending = repeatRecord.getIs().getEndPos();
    if (pos+34 < ending) { // give it 30 bytes of slop
      if (debugRepeat) logger.debug(" REPEAT AGAIN %d != %d%n", pos+4, ending);
      repeatPos = pos;
      return true;
    }

    if (debug) logger.debug(" REPEAT read until %d grib ending at %d header ='%s'%n", raf.getFilePointer(), ending, StringUtil2.cleanup(header));

    // check that end section is correct
    raf.seek(ending-4);
    for (int i = 0; i < 4; i++) {
      if (raf.read() != 55) {
        String clean = StringUtil2.cleanup(header);
        if (clean.length() > 40) clean = clean.substring(0,40) + "...";
        logger.warn("  REPEAT Missing End of GRIB message at pos=" + ending + " header= " + clean+" for="+raf.getLocation());
        break;
      }
    }
    lastPos = raf.getFilePointer();

    if (debugRepeat) logger.debug(" REPEAT DONE%n");
    repeatPos = -1; // no more repeats in this record
    return true;
  }

  // Count the number of records in a grib2 file.
  public static void main(String[] args) throws IOException {
    int count = 0;
    String file = (args.length > 0) ? args[0] : "Q:/cdmUnitTest/formats/grib2/LMPEF_CLM_050518_1200.grb";

    RandomAccessFile raf = new RandomAccessFile(file, "r");
    System.out.printf("Read %s%n", raf.getLocation());
    Grib2RecordScanner scan = new Grib2RecordScanner(raf);
    while (scan.hasNext()) {
      scan.next();
      count++;
    }
    raf.close();
    System.out.printf("count=%d%n",count);
  }

}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

// $Id: NcSDStructure.java 51 2006-07-12 17:13:13Z caron $

package thredds.server.opendap;

import opendap.servers.*;
import opendap.dap.BaseType;
import opendap.dap.NoSuchVariableException;

import java.io.IOException;
import java.io.DataOutputStream;
import java.util.List;
import ucar.nc2.Variable;
import ucar.nc2.Structure;
import ucar.ma2.*;

import org.slf4j.*;

/**
 * Wraps a netcdf Structure, as an SDStructure.
 *
 * @author jcaron
 */

public class NcSDStructure extends SDStructure  {
  static private Logger log = LoggerFactory.getLogger(NcSDStructure.class);

  private Structure ncVar = null;
  protected List<BaseType> memberBTlist;

  protected NcSDStructure org;
  protected StructureData sdata;

  /** Constructor.
   *  @param s the netcdf Structure
   *  @param list of the member variables
   */
  public NcSDStructure( Structure s, List<BaseType> list) {
      //super( NcDDS.escapeName(s.getShortName()));
      super(Variable.getDAPName(s));
    this.ncVar = s;

    for (BaseType aList : list) addVariable(aList, 0);
    memberBTlist = list;
  }

  public NcSDStructure( NcSDStructure org, StructureData sdata) {
    super(org.getEncodedName());
    this.org = org;
    this.sdata = sdata;
  }

   public Variable getVariable() { return ncVar; }

  // called if its scalar
  public boolean read(String datasetName, Object specialO) throws NoSuchVariableException,
    IOException {

    // read the scalar structure into memory
    StructureData sdata = ncVar.readStructure();
    setData( sdata);
    return(false);
  }

  // LOOK - should modify to use hasNetcdf.setData( StructureData) for efficiency
  public void setData(StructureData sdata) {
    int count = 0;

    StructureMembers sm = sdata.getStructureMembers();
    java.util.Enumeration vars = getVariables();
    while (vars.hasMoreElements()) {
      // loop through both structures
      HasNetcdfVariable hasNetcdf = (HasNetcdfVariable) vars.nextElement();
      StructureMembers.Member m = sm.getMember(count++);

      // extract the data and set it into the dods object
      Array data = sdata.getArray(m);
      hasNetcdf.setData( data);
    }

    setRead(true);
  }

  ////////////////////////////////////////////////////////////////////////////////
 // overrride for array of Structures
  public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce,Object specialO)
                                    throws NoSuchVariableException, DAP2ServerSideException, IOException {

    if (org == null) {
      super.serialize(dataset, sink, ce, specialO);
      return;
    }

    // use the projection info in the original
    java.util.Enumeration vars = org.getVariables();

    // run through each structure member
    StructureMembers sm = sdata.getStructureMembers();

    int count = 0;
    while (vars.hasMoreElements()) {
      HasNetcdfVariable sm_org = (HasNetcdfVariable) vars.nextElement();
      boolean isProjected = ((ServerMethods) sm_org).isProject();
      if (isProjected) {
        StructureMembers.Member m = sm.getMember(count);
        sm_org.serialize( sink, sdata, m);
      }
      count++;
    }

  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.atd.dorade;

import ucar.nc2.constants.CDM;

import java.io.RandomAccessFile;

/* $Id:DoradePARM.java 51 2006-07-12 17:13:13Z caron $ */

public class DoradePARM extends DoradeDescriptor {

    /**
     * Bad data value flag returned by getParamValues().
     */
    public static final float BAD_VALUE = Float.MAX_VALUE;

    private String paramName;
    private String paramDescription;
    private String unitName;
    private short usedPRTs;
    private short usedFrequencies;
    private float rcvrBandwidth; // MHz
    private short pulseWidth; // m
    private short polarization; // 0 horizontal, 1 vertical, 2 circular,
                                // 3 elliptical
    private short nSamples;
    private short binaryFormat; // 1 8-bit integer, 2 16-bit integer,
                                // 3 24-bit integer, 4 32-bit float,
                                // 5 16-bit float

    /**
     * 8-bit signed integer format.
     */
    public static final int FORMAT_8BIT_INT = 1;
    /**
     * 16-bit signed integer format.
     */
    public static final int FORMAT_16BIT_INT = 2;
    /**
     * 32-bit signed integer format.
     */
    public static final int FORMAT_32BIT_INT = 3;
    /**
     * 32-bit IEEE float format.
     */
    public static final int FORMAT_32BIT_FLOAT = 4;
    /**
     * 16-bit IEEE float format.
     */
    public static final int FORMAT_16BIT_FLOAT = 5;

    private String thresholdParamName;
    private float thresholdValue;
    private float scale;
    private float bias;
    private int badDataFlag;
    private DoradeRADD myRADD;

    public DoradePARM(RandomAccessFile file, boolean littleEndianData,
                      DoradeRADD radd)
            throws DescriptorException {
        byte[] data = readDescriptor(file, littleEndianData, "PARM");
        myRADD = radd;

        //
        // unpack
        //
        paramName = new String(data, 8, 8, CDM.utf8Charset).trim();
        paramDescription = new String(data, 16, 40, CDM.utf8Charset).trim();
        unitName = new String(data, 56, 8, CDM.utf8Charset).trim();
        usedPRTs = grabShort(data, 64);
        usedFrequencies = grabShort(data, 66);
        rcvrBandwidth = grabFloat(data, 68);
        pulseWidth = grabShort(data, 72);
        polarization = grabShort(data, 74);
        nSamples = grabShort(data, 76);
        binaryFormat = grabShort(data, 78);
        thresholdParamName = new String(data, 80, 8, CDM.utf8Charset).trim();
        thresholdValue = grabFloat(data, 88);
        scale = grabFloat(data, 92);
        bias = grabFloat(data, 96);
        badDataFlag = grabInt(data, 100);

        //
        // debugging output
        //
        if (verbose)
            System.out.println(this);
    }

    public String toString() {
        String s = "PARM\n";
        s += "  param name: " + paramName + "\n";
        s += "  param description: " + paramDescription + "\n";
        s += "  unit name: " + unitName + "\n";
        s += "  used PRTs: " + usedPRTs + "\n";
        s += "  used frequencies: " + usedFrequencies + "\n";
        s += "  receiver bandwidth: " + rcvrBandwidth + "\n";
        s += "  pulse width: " + pulseWidth + "\n";
        s += "  polarization: " + polarization + "\n";
        s += "  number of samples: " + nSamples + "\n";
        s += "  binary format: " + binaryFormat + "\n";
        s += "  threshold parameter: " + thresholdParamName + "\n";
        s += "  threshold value: " + thresholdValue + "\n";
        s += "  scale: " + scale + "\n";
        s += "  bias: " + bias + "\n";
        s += "  bad data flag: " + badDataFlag;
        return s;
    }

    /**
     * Get the name of this parameter.
     * @return the name of the parameter
     */
    public String getName() {
        return paramName;
    }

    // unidata added
    public int getBadDataFlag() {
        return badDataFlag;
    }

    // unidata added
    public float getThresholdValue() {
        return thresholdValue;
    }

    // unidata added
    public int getPolarization() {
        return polarization;
    }

    // unidata added
    public float getScale() {
        return scale;
    }

    // unidata added
    public String getUnitName() {
        return unitName;
    }

    // unidata added
    public int getusedPRTs() {
        return usedPRTs;
    }

    // unidata added
    public int getusedFrequencies() {
        return usedFrequencies;
    }

    // unidata added
    public int getnSamples() {
        return nSamples;
    }

    // unidata added
    public String getthresholdParamName() {
        return thresholdParamName;
    }

    /**
     * Get the units string for this parameter.
     * @return the units string
     */
    public String getUnits() {
        return unitName;
    }

    /**
     * Get the long description for this parameter.
     * @return the description string
     */
    public String getDescription() {
        return paramDescription;
    }

    /**
     * Get the binary format used for encoding this parameter.
     * Legal values are:
     *
FORMAT_8BIT_INT
     *
FORMAT_16BIT_INT
     *
FORMAT_32BIT_INT
     *
FORMAT_16BIT_FLOAT
     *
FORMAT_32BIT_FLOAT
     * @return the binary format for this parameter
     */
    public int getBinaryFormat() {
        return binaryFormat;
    }

    /**
     * Get the number of cells in a ray.
     * @return  the number of cells in a ray
     */
    public int getNCells() {
        return myRADD.getNCells();
    }

    /**
     * Get the cell spacing.  An exception is thrown if the cell spacing
     * is not constant.
     * @return  the cell spacing, in meters
     * @throws DescriptorException if the cell spacing is not constant.
     */
    public float getCellSpacing() throws DescriptorException {
        return myRADD.getCellSpacing();
    }

    /**
     * Get the unpacked data values for a selected parameter.
     * @param rdat the name of the desired parameter
     * @return the unpacked data values for all cells, using BAD_VALUE
     * for bad data cells
     * @throws DescriptorException
     */
    public float[] getParamValues(DoradeRDAT rdat)
            throws DescriptorException {
	return getParamValues(rdat, null);
    }

    /**
     * Get the unpacked data values for a selected parameter.
     * @param rdat the name of the desired parameter
     * @param workingArray If non-null and the same length as needed then use this.
     * @return the unpacked data values for all cells, using BAD_VALUE
     * for bad data cells
     * @throws DescriptorException
     */
    public float[] getParamValues(DoradeRDAT rdat,float[] workingArray)
            throws DescriptorException {

        if (! paramName.equals(rdat.getParamName()))
            throw new DescriptorException("parameter name mismatch");

        byte[] paramData = rdat.getRawData();

        int nCells = myRADD.getNCells();

        float[] values;
	if(workingArray!=null && workingArray.length == nCells) {
	    values = workingArray;
	} else {
	    values = new float[nCells];
	}

        short[] svalues = null;
        if (myRADD.getCompressionScheme() == DoradeRADD.COMPRESSION_HRD) {
            if (binaryFormat != DoradePARM.FORMAT_16BIT_INT) {
                throw new DescriptorException("Cannot unpack " +
                           "compressed data with binary format " +
                           binaryFormat);
            }
            svalues = uncompressHRD(paramData, nCells);
        }

        for (int cell = 0; cell < nCells; cell++) {
            switch (binaryFormat) {
                case DoradePARM.FORMAT_8BIT_INT:
                    byte bval = paramData[cell];
                    values[cell] = (bval == badDataFlag) ?
                                   BAD_VALUE : (bval - bias) / scale;
                    break;
                case DoradePARM.FORMAT_16BIT_INT:
                    short sval = (svalues != null) ?
                                 svalues[cell] : grabShort(paramData, 2 * cell);
                    values[cell] = (sval == badDataFlag) ?
                                   BAD_VALUE : (sval - bias) / scale;
                    break;
                case DoradePARM.FORMAT_32BIT_INT:
                    int ival = grabInt(paramData, 4 * cell);
                    values[cell] = (ival == badDataFlag) ?
                                   BAD_VALUE : (ival - bias) / scale;
                    break;
                case DoradePARM.FORMAT_32BIT_FLOAT:
                    float fval = grabFloat(paramData, 4 * cell);
                    values[cell] = (fval == badDataFlag) ?
                                   BAD_VALUE : (fval - bias) / scale;
                    break;
                case DoradePARM.FORMAT_16BIT_FLOAT:
                    throw new DescriptorException("can't unpack 16-bit " +
                                                        "float data yet");
                default:
                    throw new DescriptorException("bad binary format (" +
                                                        binaryFormat + ")");
            }
        }
        return values;
    }

    /**
     * Unpack MIT/HRD-compressed data into an array of exactly nCells shorts.
     * @param compressedData the raw HRD-compressed data array
     * @return an array of nCells unpacked short values
     * @throws DescriptorException
     */
    private short[] uncompressHRD(byte[] compressedData, int nCells)
            throws DescriptorException {
        short[] svalues = new short[nCells];

        int cPos = 0; // position in the compressed data, in bytes

        int nextCell = 0;
        int runLength;
        for (;; nextCell += runLength) {
            //
            // Each run begins with a 16-bin run descriptor.  The
            // high order bit is set if the run consists of bad flags.
            // The remaining 15 bits tell the length of the run.
            // A run length of 1 indicates the end of compressed data.
            //
            short runDescriptor = grabShort(compressedData, cPos);
            cPos += 2;

            boolean runHasGoodValues = ((runDescriptor & 0x8000) != 0);
            runLength = runDescriptor & 0x7fff;

            if (runLength == 1)
                break;
            //
            // Sanity check on run length
            //
            if ((nextCell + runLength) > nCells)
                throw new DescriptorException("attempt to unpack " +
                                                    "too many cells");
            //
            // If the run contains good values, then the next runLength
            // values in the compressed data stream are real values.  Otherwise
            // we need to fill with runLength bad value flags.
            //
            for (int cell = nextCell; cell < nextCell + runLength; cell++) {
                if (runHasGoodValues) {
                    svalues[cell] = grabShort(compressedData, cPos);
                    cPos += 2;
                } else {
                    svalues[cell] = (short)badDataFlag;
                }
            }
        }
        //
        // Fill the remainder of the array (if any) with bad value flags
        //
        for (int cell = nextCell; cell < nCells; cell++)
            svalues[cell] = (short)badDataFlag;

        return svalues;
    }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.unidata.geoloc.projection;

import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.sat.BoundingBoxHelper;

/**
 * Vertical Perspective Projection, spherical earth.
 * <p/>
 * See John Snyder, Map Projections used by the USGS, Bulletin 1532,
 * 2nd edition (1983), p 176
 *
 * @author Unidata Development Team
 * @see Projection
 * @see ProjectionImpl
 */

public class VerticalPerspectiveView extends ProjectionImpl {

  private double lat0, lon0; // center lat/lon in radians
  private final double false_east, false_north;
  private double R, H;

  // constants from Snyder's equations
  private double P, lon0Degrees;
  private double cosLat0, sinLat0;
  private double maxR; // "map limit" circle of this radius from the origin, p 173

  @Override
  public ProjectionImpl constructCopy() {
    ProjectionImpl result =  new VerticalPerspectiveView(getOriginLat(), getOriginLon(), R, getHeight(), false_east, false_north);
    result.setDefaultMapArea(defaultMapArea);
    result.setName(name);
    return result;
  }

  /**
   * Constructor with default parameters
   */
  public VerticalPerspectiveView() {
    this(0.0, 0.0, EARTH_RADIUS, 35800);
  }

  /**
   * Construct a VerticalPerspectiveView Projection
   *
   * @param lat0        lat origin of the coord. system on the projection plane
   * @param lon0        lon origin of the coord. system on the projection plane
   * @param earthRadius radius of the earth (km)
   * @param distance    height above the earth (km)
   */
  public VerticalPerspectiveView(double lat0, double lon0, double earthRadius, double distance) {
    this(lat0, lon0, earthRadius, distance, 0, 0);
  }

  /**
   * Construct a VerticalPerspectiveView Projection
   *
   * @param lat0           lat origin of the coord. system on the projection plane
   * @param lon0           lon origin of the coord. system on the projection plane
   * @param earthRadius    radius of the earth (km)
   * @param distance       height above the earth  (km)
   * @param false_easting  easting offset (km)
   * @param false_northing northing offset (km)
   */
  public VerticalPerspectiveView(double lat0, double lon0,
                                 double earthRadius, double distance,
                                 double false_easting,
                                 double false_northing) {

    super("VerticalPerspectiveView", false);

    this.lat0 = Math.toRadians(lat0);
    this.lon0 = Math.toRadians(lon0);
    R = earthRadius;
    H = distance;
    false_east = false_easting;
    false_north = false_northing;

    precalculate();

    addParameter(CF.GRID_MAPPING_NAME, CF.VERTICAL_PERSPECTIVE);
    addParameter(CF.LATITUDE_OF_PROJECTION_ORIGIN, lat0);
    addParameter(CF.LONGITUDE_OF_PROJECTION_ORIGIN, lon0);
    addParameter(CF.EARTH_RADIUS, earthRadius * 1000);
    addParameter(CF.PERSPECTIVE_POINT_HEIGHT, distance * 1000);
    if (false_easting != 0 || false_northing != 0) {
      addParameter(CF.FALSE_EASTING, false_easting);
      addParameter(CF.FALSE_NORTHING, false_northing);
      addParameter(CDM.UNITS, "km");
    }
  }

  /**
   * Precalculate some stuff
   */
  private void precalculate() {
    sinLat0 = Math.sin(lat0);
    cosLat0 = Math.cos(lat0);
    lon0Degrees = Math.toDegrees(lon0);
    P = 1.0 + H / R;

    // "map limit" circle of this radius from the origin, p 173
    maxR = .99 * R * Math.sqrt((P - 1) / (P + 1));
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    VerticalPerspectiveView that = (VerticalPerspectiveView) o;

    if (Double.compare(that.H, H) != 0) return false;
    if (Double.compare(that.R, R) != 0) return false;
    if (Double.compare(that.false_east, false_east) != 0) return false;
    if (Double.compare(that.false_north, false_north) != 0) return false;
    if (Double.compare(that.lat0, lat0) != 0) return false;
    if (Double.compare(that.lon0, lon0) != 0) return false;
    if ((defaultMapArea == null) != (that.defaultMapArea == null)) return false; // common case is that these are null
    if (defaultMapArea != null && !that.defaultMapArea.equals(defaultMapArea)) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result;
    long temp;
    temp = lat0 != +0.0d ? Double.doubleToLongBits(lat0) : 0L;
    result = (int) (temp ^ (temp >>> 32));
    temp = lon0 != +0.0d ? Double.doubleToLongBits(lon0) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = false_east != +0.0d ? Double.doubleToLongBits(false_east) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = false_north != +0.0d ? Double.doubleToLongBits(false_north) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = R != +0.0d ? Double.doubleToLongBits(R) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = H != +0.0d ? Double.doubleToLongBits(H) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    return result;
  }

// bean properties

  /**
   * Get the height above the earth
   *
   * @return the height above the earth
   */
  public double getHeight() {
    return H;
  }

  /**
   * Get the origin longitude in degrees
   *
   * @return the origin longitude.
   */
  public double getOriginLon() {
    return Math.toDegrees(lon0);
  }

  /**
   * Get the origin latitude in degrees
   *
   * @return the origin latitude.
   */
  public double getOriginLat() {
    return Math.toDegrees(lat0);
  }

    //////////////////////////////////////////////
  // setters for IDV serialization - do not use except for object creating

  /**
   * Set the origin longitude.
   * @param lon   the origin longitude.
   */
  public void setOriginLon(double lon) {
      lon0 = Math.toRadians(lon);
      precalculate();
  }

  /**
   * Set the height above the earth
   * @param height   height above the earth
   */
  public void setHeight(double height) {
      H = height;
      precalculate();
  }

  /**
   * Set the origin latitude.
   *
   * @param lat   the origin latitude.
   */
  public void setOriginLat(double lat) {
      lat0 = Math.toRadians(lat);
      precalculate();
  }

  /**
   * Get the label to be used in the gui for this type of projection
   *
   * @return Type label
   */
  public String getProjectionTypeLabel() {
    return "VerticalPerspectiveView";
  }

  /**
   * Create a String of the parameters.
   *
   * @return a String of the parameters
   */
  public String paramsToString() {
    return toString();
  }

  @Override
  public String toString() {
    return "VerticalPerspectiveView{" +
            "lat0=" + lat0 +
            ", lon0=" + lon0 +
            ", false_east=" + false_east +
            ", false_north=" + false_north +
            ", R=" + R +
            ", H=" + H +
            ", P=" + P +
            '}';
  }

  /**
   * This returns true when the line between pt1 and pt2 crosses the seam.
   * When the cone is flattened, the "seam" is lon0 +- 180.
   *
   * @param pt1 point 1
   * @param pt2 point 2
   * @return true when the line between pt1 and pt2 crosses the seam.
   */
  public boolean crossSeam(ProjectionPoint pt1, ProjectionPoint pt2) {
    // either point is infinite
    if (ProjectionPointImpl.isInfinite(pt1)
            || ProjectionPointImpl.isInfinite(pt2)) {
      return true;
    }
    // opposite signed X values, larger then 5000 km
    return (pt1.getX() * pt2.getX() < 0)
            && (Math.abs(pt1.getX() - pt2.getX()) > 5000.0);
  }

  /*MACROBODY
    latLonToProj {} {
      fromLat = Math.toRadians(fromLat);
      double lonDiff =
          Math.toRadians(LatLonPointImpl.lonNormal(fromLon-lon0Degrees));
      double cosc = sinLat0*Math.sin(fromLat) + cosLat0*Math.cos(fromLat)*Math.cos(lonDiff);
      double ksp = (P-1.0)/(P-cosc);
      if (cosc < 1.0/P) {
          toX = Double.POSITIVE_INFINITY;
          toY = Double.POSITIVE_INFINITY;
      } else {
          toX = false_east + R*ksp*Math.cos(fromLat)*Math.sin(lonDiff);
          toY = false_north + R*ksp*(cosLat0*Math.sin(fromLat) - sinLat0*Math.cos(fromLat)*Math.cos(lonDiff));
      }
    }

    projToLatLon {} {

      fromX = fromX - false_east;
      fromY = fromY - false_north;
      double rho = Math.sqrt(fromX*fromX + fromY*fromY);
      double r = rho /R;
      double con = P - 1.0;
      double com = P + 1.0;
      double c = Math.asin((P - Math.sqrt(1.0 - (r * r * com) / con)) / (con / r + r/con));

      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c)*sinLat0 + (fromY*Math.sin(c)*cosLat0/rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) { // not 90 or -90
          temp = rho*cosLat0*Math.cos(c) - fromY*sinLat0*Math.sin(c);
          toLon = lon0 + Math.atan(fromX*Math.sin(c)/temp);
        } else if (lat0 == PI_OVER_4) {
          toLon = lon0 + Math.atan(fromX/-fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX/fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat= Math.toDegrees(toLat);
      toLon= Math.toDegrees(toLon);
      if (temp < 0) toLon += 180;
      toLon= LatLonPointImpl.lonNormal(toLon);
    }

  MACROBODY*/

  /*BEGINGENERATED*/

  /*
  Note this section has been generated using the convert.tcl script.
  This script, run as:
  tcl convert.tcl VerticalPerspectiveView.java
  takes the actual projection conversion code defined in the MACROBODY
  section above and generates the following 6 methods
  */

  /**
   * Convert a LatLonPoint to projection coordinates
   *
   * @param latLon convert from these lat, lon coordinates
   * @param result the object to write to
   * @return the given result
   */
  public ProjectionPoint latLonToProj(LatLonPoint latLon,
                                      ProjectionPointImpl result) {
    double toX, toY;
    double fromLat = latLon.getLatitude();
    double fromLon = latLon.getLongitude();

    fromLat = Math.toRadians(fromLat);
    double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
            - lon0Degrees));
    double cosc = sinLat0 * Math.sin(fromLat)
            + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);
    double ksp = (P - 1.0) / (P - cosc);
    if (cosc < 1.0 / P) {
      toX = Double.POSITIVE_INFINITY;
      toY = Double.POSITIVE_INFINITY;
    } else {
      toX = false_east
              + R * ksp * Math.cos(fromLat) * Math.sin(lonDiff);
      toY = false_north
              + R * ksp
              * (cosLat0 * Math.sin(fromLat)
              - sinLat0 * Math.cos(fromLat) * Math.cos(lonDiff));
    }

    result.setLocation(toX, toY);
    return result;
  }

  /**
   * Convert projection coordinates to a LatLonPoint
   * Note: a new object is not created on each call for the return value.
   *
   * @param world  convert from these projection coordinates
   * @param result the object to write to
   * @return LatLonPoint convert to these lat/lon coordinates
   */
  public LatLonPoint projToLatLon(ProjectionPoint world,
                                  LatLonPointImpl result) {
    double toLat, toLon;
    double fromX = world.getX();
    double fromY = world.getY();

    fromX = fromX - false_east;
    fromY = fromY - false_north;
    double rho = Math.sqrt(fromX * fromX + fromY * fromY);
    double r = rho / R;
    double con = P - 1.0;
    double com = P + 1.0;
    double c = Math.asin((P - Math.sqrt(1.0 - (r * r * com) / con))
            / (con / r + r / con));

    toLon = lon0;
    double temp = 0;
    if (Math.abs(rho) > TOLERANCE) {
      toLat = Math.asin(Math.cos(c) * sinLat0
              + (fromY * Math.sin(c) * cosLat0 / rho));
      if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
        temp = rho * cosLat0 * Math.cos(c)
                - fromY * sinLat0 * Math.sin(c);
        toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
      } else if (Double.compare(lat0, PI_OVER_4) == 0) {
        toLon = lon0 + Math.atan(fromX / -fromY);
        temp = -fromY;
      } else {
        toLon = lon0 + Math.atan(fromX / fromY);
        temp = fromY;
      }
    } else {
      toLat = lat0;
    }
    toLat = Math.toDegrees(toLat);
    toLon = Math.toDegrees(toLon);
    if (temp < 0) {
      toLon += 180;
    }
    toLon = LatLonPointImpl.lonNormal(toLon);

    result.setLatitude(toLat);
    result.setLongitude(toLon);
    return result;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from     array of lat/lon coordinates: from[2][n],
   *                 where from[0][i], from[1][i] is the (lat,lon)
   *                 coordinate of the ith point
   * @param to       resulting array of projection coordinates,
   *                 where to[0][i], to[1][i] is the (x,y) coordinate
   *                 of the ith point
   * @param latIndex index of latitude in "from"
   * @param lonIndex index of longitude in "from"
   * @return the "to" array.
   */
  public float[][] latLonToProj(float[][] from, float[][] to, int latIndex,
                                int lonIndex) {
    int cnt = from[0].length;
    float[] fromLatA = from[latIndex];
    float[] fromLonA = from[lonIndex];
    float[] resultXA = to[INDEX_X];
    float[] resultYA = to[INDEX_Y];
    double toX, toY;

    for (int i = 0; i < cnt; i++) {
      double fromLat = fromLatA[i];
      double fromLon = fromLonA[i];

      fromLat = Math.toRadians(fromLat);
      double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
              - lon0Degrees));
      double cosc = sinLat0 * Math.sin(fromLat)
              + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);
      double ksp = (P - 1.0) / (P - cosc);
      if (cosc < 1.0 / P) {
        toX = Double.POSITIVE_INFINITY;
        toY = Double.POSITIVE_INFINITY;
      } else {
        toX = false_east
                + R * ksp * Math.cos(fromLat) * Math.sin(lonDiff);
        toY = false_north
                + R * ksp
                * (cosLat0 * Math.sin(fromLat)
                - sinLat0 * Math.cos(fromLat) * Math.cos(lonDiff));
      }

      resultXA[i] = (float) toX;
      resultYA[i] = (float) toY;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from array of lat/lon coordinates: from[2][n], where
   *             (from[0][i], from[1][i]) is the (lat,lon) coordinate
   *             of the ith point
   * @param to   resulting array of projection coordinates: to[2][n]
   *             where (to[0][i], to[1][i]) is the (x,y) coordinate
   *             of the ith point
   * @return the "to" array
   */
  public float[][] projToLatLon(float[][] from, float[][] to) {
    int cnt = from[0].length;
    float[] fromXA = from[INDEX_X];
    float[] fromYA = from[INDEX_Y];
    float[] toLatA = to[INDEX_LAT];
    float[] toLonA = to[INDEX_LON];

    double toLat, toLon;
    for (int i = 0; i < cnt; i++) {
      double fromX = fromXA[i];
      double fromY = fromYA[i];

      fromX = fromX - false_east;
      fromY = fromY - false_north;
      double rho = Math.sqrt(fromX * fromX + fromY * fromY);
      double r = rho / R;
      double con = P - 1.0;
      double com = P + 1.0;
      double c = Math.asin((P - Math.sqrt(1.0 - (r * r * com) / con))
              / (con / r + r / con));

      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c) * sinLat0
                + (fromY * Math.sin(c) * cosLat0 / rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
          temp = rho * cosLat0 * Math.cos(c)
                  - fromY * sinLat0 * Math.sin(c);
          toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
        } else if (Double.compare(lat0, PI_OVER_4) == 0) {
          toLon = lon0 + Math.atan(fromX / -fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX / fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat = Math.toDegrees(toLat);
      toLon = Math.toDegrees(toLon);
      if (temp < 0) {
        toLon += 180;
      }
      toLon = LatLonPointImpl.lonNormal(toLon);

      toLatA[i] = (float) toLat;
      toLonA[i] = (float) toLon;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from     array of lat/lon coordinates: from[2][n],
   *                 where from[0][i], from[1][i] is the (lat,lon)
   *                 coordinate of the ith point
   * @param to       resulting array of projection coordinates,
   *                 where to[0][i], to[1][i] is the (x,y) coordinate
   *                 of the ith point
   * @param latIndex index of latitude in "from"
   * @param lonIndex index of longitude in "from"
   * @return the "to" array.
   */
  public double[][] latLonToProj(double[][] from, double[][] to,
                                 int latIndex, int lonIndex) {
    int cnt = from[0].length;
    double[] fromLatA = from[latIndex];
    double[] fromLonA = from[lonIndex];
    double[] resultXA = to[INDEX_X];
    double[] resultYA = to[INDEX_Y];
    double toX, toY;

    for (int i = 0; i < cnt; i++) {
      double fromLat = fromLatA[i];
      double fromLon = fromLonA[i];

      fromLat = Math.toRadians(fromLat);
      double lonDiff = Math.toRadians(LatLonPointImpl.lonNormal(fromLon
              - lon0Degrees));
      double cosc = sinLat0 * Math.sin(fromLat)
              + cosLat0 * Math.cos(fromLat) * Math.cos(lonDiff);
      double ksp = (P - 1.0) / (P - cosc);
      if (cosc < 1.0 / P) {
        toX = Double.POSITIVE_INFINITY;
        toY = Double.POSITIVE_INFINITY;
      } else {
        toX = false_east
                + R * ksp * Math.cos(fromLat) * Math.sin(lonDiff);
        toY = false_north
                + R * ksp
                * (cosLat0 * Math.sin(fromLat)
                - sinLat0 * Math.cos(fromLat) * Math.cos(lonDiff));
      }

      resultXA[i] = toX;
      resultYA[i] = toY;
    }
    return to;
  }

  /**
   * Convert lat/lon coordinates to projection coordinates.
   *
   * @param from array of lat/lon coordinates: from[2][n], where
   *             (from[0][i], from[1][i]) is the (lat,lon) coordinate
   *             of the ith point
   * @param to   resulting array of projection coordinates: to[2][n]
   *             where (to[0][i], to[1][i]) is the (x,y) coordinate
   *             of the ith point
   * @return the "to" array
   */
  public double[][] projToLatLon(double[][] from, double[][] to) {
    int cnt = from[0].length;
    double[] fromXA = from[INDEX_X];
    double[] fromYA = from[INDEX_Y];
    double[] toLatA = to[INDEX_LAT];
    double[] toLonA = to[INDEX_LON];

    double toLat, toLon;
    for (int i = 0; i < cnt; i++) {
      double fromX = fromXA[i];
      double fromY = fromYA[i];

      fromX = fromX - false_east;
      fromY = fromY - false_north;
      double rho = Math.sqrt(fromX * fromX + fromY * fromY);
      double r = rho / R;
      double con = P - 1.0;
      double com = P + 1.0;
      double c = Math.asin((P - Math.sqrt(1.0 - (r * r * com) / con))
              / (con / r + r / con));

      toLon = lon0;
      double temp = 0;
      if (Math.abs(rho) > TOLERANCE) {
        toLat = Math.asin(Math.cos(c) * sinLat0
                + (fromY * Math.sin(c) * cosLat0 / rho));
        if (Math.abs(lat0 - PI_OVER_4) > TOLERANCE) {  // not 90 or -90
          temp = rho * cosLat0 * Math.cos(c)
                  - fromY * sinLat0 * Math.sin(c);
          toLon = lon0 + Math.atan(fromX * Math.sin(c) / temp);
        } else if (Double.compare(lat0, PI_OVER_4) == 0) {
          toLon = lon0 + Math.atan(fromX / -fromY);
          temp = -fromY;
        } else {
          toLon = lon0 + Math.atan(fromX / fromY);
          temp = fromY;
        }
      } else {
        toLat = lat0;
      }
      toLat = Math.toDegrees(toLat);
      toLon = Math.toDegrees(toLon);
      if (temp < 0) {
        toLon += 180;
      }
      toLon = LatLonPointImpl.lonNormal(toLon);

      toLatA[i] = toLat;
      toLonA[i] = toLon;
    }
    return to;
  }

  /*ENDGENERATED*/

  /**
   * Create a ProjectionRect from the given LatLonRect.
   * Handles lat/lon points that do not intersect the projection panel.
   *
   * @param rect the LatLonRect
   * @return ProjectionRect, or null if no part of the LatLonRect intersects the projection plane
   */
  @Override
 public ProjectionRect latLonToProjBB(LatLonRect rect) {
    BoundingBoxHelper bbhelper = new BoundingBoxHelper(this, maxR);
    return bbhelper.latLonToProjBB(rect);
  }

  private static void test(double lat, double lon) {
    double radius = 6371.0;

    VerticalPerspectiveView a = new VerticalPerspectiveView(0, 0, radius, 5.62 * radius);
    ProjectionPoint p = a.latLonToProj(lat, lon);
    System.out.println("-----\nproj point = " + p);
    System.out.println("x/r = " + p.getX() / radius); // see snyder p 174
    System.out.println("y/r = " + p.getY() / radius);

    LatLonPoint ll = a.projToLatLon(p);
    System.out.println(" lat = " + ll.getLatitude() + " should be= " + lat);
    System.out.println(" lon = " + ll.getLongitude() + " should be= " + lon);
  }

  public static void main(String[] args) {
    double radius = 6371.0;
    double height = 35747.0;

    VerticalPerspectiveView a = new VerticalPerspectiveView(0, 0, radius, height);

    double limit = .99 * Math.sqrt((a.P - 1) / (a.P + 1));
    System.out.println(" limit = " + limit);
    System.out.println(" limit*90 = " + limit * 90);

    /* whats the min and max lat/lon ?
    double theta = 0;
    while (theta <= 360) {
      double x = limit*radius * Math.cos(Math.toRadians(theta));
      double y = limit*radius * Math.sin(Math.toRadians(theta));
      LatLonPointImpl llpt = (LatLonPointImpl) a.projToLatLon( new ProjectionPointImpl(x, y));
      System.out.println(theta+" = "+llpt.toString());
      theta += 15;
    } */

    LatLonRect rect = new LatLonRect(new LatLonPointImpl(-45.0, -45.0), -45.0, -45.0);
    ProjectionRect r = a.latLonToProjBB(rect);
    System.out.println(" ProjectionRect result = " + r);

    /*  double minx, maxx, miny, maxy;

    // first clip the request rectangle to the bounding box of the grid
    LatLonRect bb = getLatLonBoundingBox();
    rect = bb.intersect( rect);
    if (null == rect)
      throw new InvalidRangeException("Request Bounding box does not intersect Grid");

    LatLonPointImpl llpt = rect.getLowerLeftPoint();
    LatLonPointImpl urpt = rect.getUpperRightPoint();
    LatLonPointImpl lrpt = rect.getLowerRightPoint();
    LatLonPointImpl ulpt = rect.getUpperLeftPoint();

    if (isLatLon()) {
      minx = getMinOrMaxLon(llpt.getLongitude(), ulpt.getLongitude(), true);
      miny = Math.min(llpt.getLatitude(), lrpt.getLatitude());
      maxx = getMinOrMaxLon(urpt.getLongitude(), lrpt.getLongitude(), false);
      maxy = Math.min(ulpt.getLatitude(), urpt.getLatitude());

    } else {
      Projection dataProjection = getProjection();
      ProjectionPoint ll = dataProjection.latLonToProj(llpt, new ProjectionPointImpl());
      ProjectionPoint ur = dataProjection.latLonToProj(urpt, new ProjectionPointImpl());
      ProjectionPoint lr = dataProjection.latLonToProj(lrpt, new ProjectionPointImpl());
      ProjectionPoint ul = dataProjection.latLonToProj(ulpt, new ProjectionPointImpl());

      minx = Math.min(ll.getX(), ul.getX());
      miny = Math.min(ll.getY(), lr.getY());
      maxx = Math.max(ur.getX(), lr.getX());
      maxy = Math.max(ul.getY(), ur.getY());
    }  */
  }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.grib.grib2.table;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import ucar.nc2.grib.GribNumbers;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * Read and process WMO GRIB templates from standard WMO source file.
 *
 * @author caron
 * @since Jul 31, 2010
 */

public class WmoTemplateTables {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(WmoTemplateTables.class);
  private static final Map<String, String> convertMap = new HashMap<>();
  static {
    // gds
    convertMap.put("Source of Grid Definition (see code table 3.0)", "3.0");
    convertMap.put("Shape of the Earth", "3.2");
    convertMap.put("Interpretation of list of numbers", "3.11");

    // pds
    convertMap.put("Type of generating process", "4.3");
    convertMap.put("Indicator of unit of time range", "4.4");
    convertMap.put("Indicator of unit of time for time range over which statistical processing is done", "4.4");
    convertMap.put("Indicator of unit of time for the increment between the successive fields used", "4.4");
    convertMap.put("Type of first fixed surface", "4.5");
    convertMap.put("Type of second fixed surface", "4.5");
    convertMap.put("Type of ensemble forecast", "4.6");
    convertMap.put("Derived forecast", "4.7");
    convertMap.put("Probability type", "4.9");
    convertMap.put(
        "Statistical process used to calculate the processed field from the field at each time increment during the time range",
        "4.10");
    convertMap.put("Type of time increment between successive fields used in the statistical processing", "4.11");

    convertMap.put("Analysis or forecast generating process identifier (defined by originating centre)", "ProcessId"); // ??
    convertMap.put("Background generating process identifier (defined by originating centre)", "ProcessId");
    convertMap.put("Forecast generating process identifier (defined by originating centre)", "ProcessId");
  }

  public static final Version standard = Version.GRIB2_22_0_0;
  public enum Version {
    GRIB2_22_0_0;

    String getResourceName() {
      return "/resources/grib2/wmo/" + this.name() + "_Template_en.xml";
    }

    @Nullable
    String[] getElemNames() {
      if (this == GRIB2_22_0_0) {
        return new String[]{"GRIB2_22_0_0_Template_en", "Title_en", "Note_en", "Contents_en"};
      }
      return null;
    }
  }

  private static WmoTemplateTables instance = null;

  public static WmoTemplateTables getInstance() {
    if (instance == null) {
      instance = new WmoTemplateTables();
      try {
        instance.readXml(standard);
      } catch (IOException e) {
        logger.error("Cant read WMO Grib2 tables");
        throw new RuntimeException(e);
      }
    }
    return instance;
  }

  /*
  <GRIB2_22_0_0_Template_en>
    <No>1451</No>
    <Title_en>Product definition template 4.55 - spatio-temporal changing tiles at a horizontal level or horizontal layer at a point in time</Title_en>
    <OctetNo>35</OctetNo>
    <Contents_en>Type of second fixed surface</Contents_en>
    <Note_en>(see Code table 4.5)</Note_en>
    <Status>Operational</Status>
  </GRIB2_22_0_0_Template_en>
  */

  private void readXml(Version version) throws IOException {
    try (InputStream ios = WmoTemplateTables.class.getResourceAsStream(version.getResourceName())) {
      if (ios == null) {
        throw new IOException("cant open TemplateTable %s " + version.getResourceName());
      }

      org.jdom2.Document doc;
      try {
        SAXBuilder builder = new SAXBuilder();
        doc = builder.build(ios);
      } catch (JDOMException e) {
        throw new IOException(e.getMessage());
      }

      Map<String, TemplateTable> map = new HashMap<>();
      String[] elems = version.getElemNames();
      assert elems != null;
      assert elems.length > 3;

      Element root = doc.getRootElement();
      List<Element> featList = root.getChildren(elems[0]); // 0 = main element
      for (Element elem : featList) {
        String desc = elem.getChildTextNormalize(elems[1]); // 1 = title
        String octet = elem.getChildTextNormalize("OctetNo");
        String content = elem.getChildTextNormalize(elems[3]); // 3 = content
        String status = elem.getChildTextNormalize("Status");
        String note = elem.getChildTextNormalize(elems[2]); // 2 == note

        TemplateTable template = map.computeIfAbsent(desc, name -> new TemplateTable(name));
        template.add(octet, content, status, note);
      }
      ios.close();

      List<TemplateTable> tlist = new ArrayList<>(map.values());
      for (TemplateTable t : tlist) {
        if (t.m1 == 3) {
          t.add(1, 4, "GDS length");
          t.add(5, 1, "Section");
          t.add(6, 1, "Source of Grid Definition (see code table 3.0)");
          t.add(7, 4, "Number of data points");
          t.add(11, 1, "Number of octects for optional list of numbers");
          t.add(12, 1, "Interpretation of list of numbers");
          t.add(13, 2, "Grid Definition Template Number");

        } else if (t.m1 == 4) {
          t.add(1, 4, "PDS length");
          t.add(5, 1, "Section");
          t.add(6, 2, "Number of coordinates values after Template");
          t.add(8, 2, "Product Definition Template Number");
        }
        Collections.sort(t.flds);
      }

      this.templateTables = map.values().stream().sorted().collect(ImmutableList.toImmutableList());
      ImmutableMap.Builder<String, TemplateTable> builder = ImmutableMap.builder();
      map.values().forEach(t -> builder.put(t.getId(), t));
      this.templateMap = builder.build();
    }
  }

  private ImmutableList<TemplateTable> templateTables;
  private ImmutableMap<String, TemplateTable> templateMap;

  public ImmutableList<TemplateTable> getTemplateTables() {
    return templateTables;
  }

  @Nullable
  public TemplateTable getTemplateTable(String key) {
    return templateMap.get(key);
  }

  public class TemplateTable implements Comparable<TemplateTable> {
    private String name, desc;
    private int m1, m2;
    private final List<Field> flds = new ArrayList<>();

    private TemplateTable(String desc) {
      this.desc = desc;

      String[] slist = desc.split(" ");
      for (int i = 0; i < slist.length; i++) {
        if (slist[i].equalsIgnoreCase("template")) {
          name = slist[i + 1];
          String[] slist2 = name.split("\\.");
          if (slist2.length == 2) {
            m1 = Integer.parseInt(slist2[0]);
            m2 = Integer.parseInt(slist2[1]);
          } else {
            logger.warn("TemplateTable bad= {}", name);
          }
          break;
        }
      }
    }

    private void add(String octet, String content, String status, String note) {
      flds.add(new Field(octet, content, status, note));
    }

    private void add(int start, int nbytes, String content) {
      flds.add(new Field(start, nbytes, content));
    }

    public String getId() {
      return m1 + "." + m2;
    }

    public String getName() {
      return name;
    }

    public String getDesc() {
      return desc;
    }

    public int getM1() {
      return m1;
    }

    public int getM2() {
      return m2;
    }

    public ImmutableList<Field> getFlds() {
      return ImmutableList.copyOf(flds);
    }

    @Override
    public int compareTo(@Nonnull TemplateTable o) {
      if (m1 == o.m1) {
        return m2 - o.m2;
      } else {
        return m1 - o.m1;
      }
    }

    public void showInfo(Grib2Tables tables, byte[] raw, Formatter f) {
      f.format("%n(%s) %s %n", name, desc);
      for (Field fld : flds) {
        if (fld.start < 0) {
          continue;
        }

        String info = convertMap.get(fld.content);
        if (info == null) {
          f.format("%3d: %90s == %d %n", fld.start, fld.content, fld.value(raw));
        } else {
          String desc = convert(tables, info, fld.value(raw));
          if (desc == null) {
            f.format("%3d: %90s == %d (%s) %n", fld.start, fld.content, fld.value(raw), convert(tables, info, fld.value(raw)));
          } else {
            f.format("%3d: %90s == %d (table %s: %s) %n", fld.start, fld.content, fld.value(raw), info, desc);
          }
        }
      }
    }

    private String convert(Grib2Tables tables, String table, int value) {
      String result = tables.getTableValue(table, value);
      return (result != null) ? result : "Table " + table + " code " + value + " not found";
    }
  }

  public class Field implements Comparable<Field> {
    private final String octet;
    private final String content;
    private String status;
    private String note;
    private int start, nbytes;

    private Field(String octet, String content, String status, String note) {
      this.octet = octet;
      this.content = content;
      this.status = status;
      this.note = note;

      try {
        int pos = octet.indexOf('-');
        if (pos > 0) {
          start = Integer.parseInt(octet.substring(0, pos));
          String stops = octet.substring(pos + 1);
          try {
            int stop = Integer.parseInt(stops);
            nbytes = stop - start + 1;
          } catch (Exception e) {
            logger.debug("Error parsing wmo template line=" + content, e);
          }
        } else {
          start = Integer.parseInt(octet);
          nbytes = 1;
        }
      } catch (Exception e) {
        start = -1;
        nbytes = 0;
      }
    }

    private Field(int start, int nbytes, String content) {
      this.start = start;
      this.nbytes = nbytes;
      this.content = content;
      this.octet = start + "-" + (start + nbytes - 1);
    }

    public String getOctet() {
      return octet;
    }

    public String getContent() {
      return content;
    }

    public String getStatus() {
      return status;
    }

    public String getNote() {
      return note;
    }

    public int getStart() {
      return start;
    }

    public int getNbytes() {
      return nbytes;
    }

    @Override
    public int compareTo(@Nonnull Field o) {
      return start - o.start;
    }

    private int value(byte[] pds) {
      switch (nbytes) {
        case 1:
          return get(pds, start);
        case 2:
          return GribNumbers.int2(get(pds, start), get(pds, start + 1));
        case 4:
          return GribNumbers.int4(get(pds, start), get(pds, start + 1), get(pds, start + 2), get(pds, start + 3));
        default:
          return -9999;
      }
    }

    private int get(byte[] pds, int offset) {
      return pds[offset - 1] & 0xff;
    }
  }
}