<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package usda.weru.weps.location;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.measure.Quantity;
import tec.uom.se.quantity.Quantities;
import javax.measure.quantity.Time;
import systems.uom.common.USCustomary;
import si.uom.SI;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 

import usda.weru.gis.latlong.LatLong;
import usda.weru.gis.GISUtil;
import usda.weru.util.Caster;
import tec.uom.se.unit.MetricPrefix;
import usda.weru.util.ConfigData;

/**
 *
 * @param &lt;S&gt; the type of the smaller subdivisions
 * @author Joseph Levin &lt;joelevin@weru.ksu.edu&gt;
 */
public abstract class Site&lt;S extends Site&lt;?&gt;&gt; implements Comparable&lt;Site&lt;?&gt;&gt; {

    private static final Logger LOGGER = LogManager.getLogger(Site.class);
    private static final Map&lt;String, Site.Level0&gt; COUNTRIES_FIPS_CHAR2;
    private static final Map&lt;String, Site&lt;?&gt;&gt; SITES_FIPS_CHAR4;
    private static final Map&lt;String, Site&lt;?&gt;&gt; SITES_HASC;
    protected String c_displayName;
    private final Designation c_type;
    protected String c_abbr;
    protected LatLong c_latlongFallback;
    protected LatLong c_latlong;

    /**
     * only needs to be unique to the parent
     */
    protected String c_primaryKey;
    protected Site&lt;?&gt; c_parent;
    protected List&lt;S&gt; c_divisions;
    protected Map&lt;String, Site&lt;?&gt;&gt; c_divisonAbbr;
    protected String c_fips;
    protected String c_fipscode;
    protected String c_hasc;

    private Site(Designation type) {
        c_type = type;
    }

    public Designation getDesgination() {
        return c_type;
    }

    public String getDisplayName() {
        return c_displayName;
    }

    public LatLong getLatLong() {
        if (c_latlong == null) {
            LOGGER.debug("Querying gis data for centroid of " + this + ".");
            LatLong centroid = GISUtil.representativeLatLong(this);
            if (centroid != null) {
                LOGGER.debug("Found centroid of " + this + ".  Centroid=" + centroid);
                c_latlong = centroid;
            } else if (c_latlongFallback != null) {
                LOGGER.debug("Unable to determine centroid for " + this + ". Using fallback latlong.");
                return c_latlongFallback;
            } else if (getParent() != null) {
                LOGGER.debug("Unable to determine centroid for " + this + ". Using parent latlong.");
                return getParent().getLatLong();
            } else {
                LOGGER.warn("Unable to determine centroid for " + this + ".");
                return null;
            }
        }
        return c_latlong;
    }

    public Site&lt;?&gt; getParent() {
        return c_parent;
    }

    public String getAbbreviation() {
        return c_abbr;
    }

    public String getPrimaryKey() {
        return c_primaryKey;
    }

    public String getFIPS() {
        return c_fips;
    }
    
    public String getFIPSCode()
    {
        return c_fipscode;
    }
    
    public void setFIPSCode(String input)
    {
        c_fipscode = input;
    }

    public String getHASC() {
        return c_hasc;
    }

    public abstract S[] getSubDivisions();

    protected synchronized List&lt;S&gt; getSubDivisionList() {
        if (c_divisions == null) {
            c_divisions = new ArrayList&lt;&gt;();
        }
        return c_divisions;
    }

    public S getSubDivision(String primaryKey) {
        for (S site : getSubDivisionList()) {
            if (primaryKey.equals(site.getPrimaryKey())) {
                return site;
            }
        }
        return null;
    }

    @Override
    public String toString() {

        return (c_parent != null ? c_parent.toString() + "-" : "") + c_primaryKey;
    }

    public static Site.Level0[] countries() {
        Collection&lt;Site.Level0&gt; temp = COUNTRIES_FIPS_CHAR2.values();
        return temp.toArray(new Site.Level0[temp.size()]);
    }

    @Override
    public int compareTo(Site&lt;?&gt; o) {
        String name = o != null ? o.getDisplayName() : null;
        return getDisplayName().compareTo(name);
    }

    public static Site&lt;?&gt; valueOfFIPS(String fips) throws IllegalArgumentException {
        fips = fips.trim();
        if (fips.length() != 4) {
            throw new IllegalArgumentException("A FIPS code must be 4 characters long.");
        }
        return Site.SITES_FIPS_CHAR4.get(fips);
    }

    public static Site&lt;?&gt; valueOfHASC(String hasc) throws IllegalArgumentException {
        if (hasc != null) {
            hasc = hasc.trim();
            return !hasc.isEmpty() ? SITES_HASC.get(hasc) : null;
        }

        return null;
    }

    public static Site&lt;?&gt; valueOf(String value) {
        if (value == null) {
            return null;
        }
        value = value.trim();
        String[] parts = value.split("\\-");

        Site&lt;?&gt; temp = null;
        for (int i = 0; i &lt; parts.length; i++) {
            if (i == 0) {
                //TODO: add support for iso codes?
                if (parts[i].toUpperCase().startsWith("FIPS:")) {
                    String code = parts[i].substring(5);
                    if (code.length() == 2) {
                        code = code + "00";
                    }
                    temp = Site.valueOfFIPS(code);

                    if (temp == null) {
                        LOGGER.error("Unknown country : " + parts[i]);
                        return null;
                    }
                } else {
                    LOGGER.debug("Unknown country code.  Should start with FIPS: " + parts[i]);
                    return null;
                }
            } else {
                Site&lt;?&gt; temp2 = temp.getSubDivision(parts[i]);
                if (temp2 != null) {
                    temp = temp2;
                } else {
                    LOGGER.error("Unknown sub division of " + temp.toString() + ": " + parts[i]);
                    return temp;
                }
            }
        }
        return temp;
    }

    public static class Level0 extends Site&lt;Level1&gt; {

        private String c_fipsChar4;
        private String c_iso3166Char2;
        private String c_iso3166Char3;
        private int c_iso3166Number;

        private Level0(Designation type, String fips) {
            super(type);
            c_fipsChar4 = fips;
        }

        @Override
        public String getPrimaryKey() {
            return getFIPSChar2();
        }

        public String getFIPSChar4() {
            return c_fipsChar4;
        }

        public String getFIPSChar2() {
            return c_fipsChar4.substring(0, 2);
        }

        public String getISO3166Char2() {
            return c_iso3166Char2;
        }

        public String getISO3166Char3() {
            return c_iso3166Char3;
        }

        public int getISO3166Number() {
            return c_iso3166Number;
        }

        @Override
        public String getAbbreviation() {
            if (super.getAbbreviation() == null) {
                return super.getAbbreviation();
            } else {
                return getFIPSChar2();
            }
        }

        @Override
        public String toString() {
            return "FIPS:" + getFIPSChar2();
        }

        @Override
        public Level1[] getSubDivisions() {
            return c_divisions != null ? c_divisions.toArray(new Level1[c_divisions.size()]) : new Level1[0];
        }
    }

    public static class Level1 extends Site&lt;Level2&gt; {

        public Level1(Level0 parent, Designation type) {
            super(type);
            c_parent = parent;
            c_fipscode = parent.getFIPSCode();
        }

        @Override
        public Level2[] getSubDivisions() {
            return c_divisions != null ? c_divisions.toArray(new Level2[c_divisions.size()]) : new Level2[0];
        }
    }

    public static class Level2 extends Site&lt;Site&lt;?&gt;&gt; {

        public Level2(Site&lt;Level2&gt; parent, Designation type) {
            super(type);
            c_parent = parent;
            c_fipscode = parent.getFIPSCode();
        }

        @Override
        public Site&lt;?&gt;[] getSubDivisions() {
            return c_divisions != null ? c_divisions.toArray(new Site&lt;?&gt;[c_divisions.size()]) : new Site&lt;?&gt;[0];
        }
    }

    /**
     * Constants from FIPS 414, may need to be updated as the fips data changes
     */
    public enum Designation {

        Country("country"),
        State("state", "federal state"),
        District("district", "metropolitan district", "london borough", "commonwealth district",
                "federal district", "capital district", "special district"),
        County("county", "urban county", "county borough"),
        Parish,
        Borough,
        Dependency,
        Emirate,
        Province("province", "constitutional province", "autonomous province"),
        Rayon,
        City("city", "municipality", "special municipality", "town", "special city", "chartered city",
                "capital city", "city corporation", "urban commune", "community", "city and county"),
        Territory("territory", "federal territory", "union territory", "national capital territory",
                "autonomous territorial unit", "territorial unit", "capital territory"),
        Municipality,
        Municipio,
        Department,
        Division,
        Republic("republic", "autonomous republic"),
        Federation,
        Region("region", "autonomous region", "special regioin"),
        Island("island", "islands", "island group", "islands area"),
        Commune,
        Prefecture("prefecture", "economic prefecture"),
        Governorate,
        Administration,
        Quarter,
        Other("special zone", "special region", "capital - special zone", "kray", "oblast",
                "cercle", "zone", "canton", "autonomous okrug", "intendancy", "unitary authority",
                "autonomous oblast", "autonomous community", "statutory community", "ward", "federal dependencies"),
        Area("area", "Pakistan-administered area", "council area", "administrative area"),
        Unknown("?");
        private final String[] c_names;

        private Designation() {
            c_names = null;
        }

        private Designation(String... names) {
            c_names = names;
        }

        public String[] getAlternativeNames() {
            return c_names != null ? c_names : new String[]{this.toString()};
        }
    }

    static {
        long startTime = System.nanoTime();
        LOGGER.info("Initializing site data.");
        //load the site data
        COUNTRIES_FIPS_CHAR2 = new HashMap&lt;&gt;();
        SITES_FIPS_CHAR4 = new HashMap&lt;&gt;();
        SITES_HASC = new HashMap&lt;&gt;();

        //read fips-414.txt
        
        // MEH-webstart read(ClassLoader.getSystemResourceAsStream("usda/weru/resources/site_fips-414.txt"), new LineHandler() {
        // For Webstart, need to use the context loader, not system loader, (system loader cannot find the file in WS)
        //read(Thread.currentThread().getContextClassLoader().getResourceAsStream("usda/weru/resources/site_fips-414.txt"), new LineHandler() {
        File inputFile;
        InputStream fileStream;
        File gisFile = new File (ConfigData.getDefault().getDataParsed(ConfigData.GISData));
        try {
        inputFile = new File (gisFile,"site_fips-414.txt");
        fileStream = new FileInputStream (inputFile);
        read(fileStream, new LineHandler() {
            @Override
            public void handle(String line) {
                try {
                    String[] parts = line.split("_", -1);
                    String code = parts[0];
                    String desginationText = parts[3];
                    Designation desgination = findDesignation(desginationText);
                    String name = parts[7];
                    code = code.trim();
                    String char2 = code.substring(0, 2);
                    String divisionString = code.substring(2);

                    if (desgination == null) {
                        LOGGER.debug("Missing desgination enum: " + desginationText);
                        desgination = Designation.Unknown;
                    }

                    //this is a country
                    switch (desgination) {
                        case Country:
                            Site.Level0 country = new Site.Level0(desgination, code);
                            country.c_displayName = name;
                            country.c_fips = code.substring(0, 2);
                            country.setFIPSCode(code.substring(0, 2));
                            //top level
                            COUNTRIES_FIPS_CHAR2.put(country.getFIPSChar2(), country);
                            SITES_FIPS_CHAR4.put(country.getFIPSChar4(), country);
                            break;
                        default:
                            Level0 parent = COUNTRIES_FIPS_CHAR2.get(char2);

                            if (parent == null) {
                                LOGGER.warn("No country found. " + char2);
                                return;
                            }

                            Level1 division = new Level1(parent, desgination);
                            division.c_displayName = name;

                            //default unique key is the four character code
                            division.c_primaryKey = code;

                            division.c_fips = divisionString;
                            
                            division.setFIPSCode(parent.getFIPSCode() + "-" + code.substring(0, 4));

                            //add to the parent
                            parent.getSubDivisionList().add(division);

                            SITES_FIPS_CHAR4.put(code, division);
                            break;

                    }

                } catch (Exception e) {
                    LOGGER.debug("Unable to load site: " + (line != null ? line : "&lt;NULL&gt;"), e);
                }

            }
        });
        } catch (Exception ex) {
        }

        //iso to fips differences
        final Map&lt;String, String&gt; isoToFips = new HashMap&lt;&gt;();

        // MEH-webstart read(ClassLoader.getSystemResourceAsStream("usda/weru/resources/site_iso2fips.txt"), new LineHandler() {
        // For Webstart, need to use the context loader, not system loader, (system loader cannot find the file in WS)
//        read(Thread.currentThread().getContextClassLoader().getResourceAsStream("usda/weru/resources/site_iso2fips.txt"), new LineHandler() {
        try {
        inputFile = new File (gisFile,"site_iso2fips.txt");
        fileStream = new FileInputStream (inputFile);
        read(fileStream, new LineHandler() {

            @Override
            public void handle(String line) {
            String[] parts = line.split("_", -1);
            String iso = parts[0];
            String fips = parts[1];
            
            isoToFips.put(iso, fips);

            }
        });
        } catch (Exception ex) {
        }

        //read iso-3166.txt
        // MEH-webstart read(ClassLoader.getSystemResourceAsStream("usda/weru/resources/site_iso-3166.txt"), new LineHandler() {
        // For Webstart, need to use the context loader, not system loader, (system loader cannot find the file in WS)
//        read(Thread.currentThread().getContextClassLoader().getResourceAsStream("usda/weru/resources/site_iso-3166.txt"), new LineHandler() {
        try {
        inputFile = new File (gisFile,"site_iso-3166.txt");
        fileStream = new FileInputStream (inputFile);
        read(fileStream, new LineHandler() {

            @Override
            public void handle(String line) {
                String[] parts = line.split("_", -1);
                String char2 = parts[0];
                String char3 = parts[1];
                int number = Integer.valueOf(parts[2]);
                //String name = parts[3];

                //handle differences between fips and iso
                String fips = isoToFips.get(char2);

                Level0 country = COUNTRIES_FIPS_CHAR2.get(fips != null ? fips : char2);
                if (country != null) {
                    country.c_iso3166Char2 = char2;
                    country.c_iso3166Char3 = char3;
                    country.c_iso3166Number = number;

                    //overide with the nicer ISO Names
                    //country.c_displayName = name;
                } else {
                    //oops
                    LOGGER.debug("No country record found: " + line);
                }

            }
        });
        } catch (Exception ex) {
        }

        //read us-counties.txt
        // MEH-webstart read(ClassLoader.getSystemResourceAsStream("usda/weru/resources/site_us-counties.txt"), new LineHandler() {
        // For Webstart, need to use the context loader, not system loader, (system loader cannot find the file in WS)
        //read(Thread.currentThread().getContextClassLoader().getResourceAsStream("usda/weru/resources/site_us-counties.txt"), new LineHandler() {
        try {
        inputFile = new File (gisFile,"site_us-counties.txt");
        fileStream = new FileInputStream (inputFile);
        read(fileStream, new LineHandler() {

            @Override
            public void handle(String line) {

                //county line
                String[] parts = line.split("_", -1);
                String stateAbbr = parts[0];
                String stateFips = parts[1];

                String countyFips = parts[2];
                String name = parts[3];

                //set the state abbr/key
                //Site state = valueOfFIPS("US" + stateFips);
                Site&lt;Level2&gt; state = Caster.&lt;Site&lt;Level2&gt;&gt;cast(Site.valueOfFIPS("US" + stateFips));
                state.setFIPSCode("US-" + line);

                if (state == null) {
                    LOGGER.debug("No state record found: " + line);
                    return;
                }
                state.c_abbr = stateAbbr;
                state.c_primaryKey = stateAbbr;

                name = name.trim();

                Designation countyEquivalent = findDesignation(parts[5]);
//                Designation countyEquivalent = Designation.County;
//                //hardcoded hack for AK, LA
//                if ("LA".equalsIgnoreCase(stateAbbr)) {
//                    countyEquivalent = Designation.Parish;
//                } else if ("AK".equalsIgnoreCase(stateAbbr)) {
//                    countyEquivalent = Designation.Borough;
//                } else if ("PR".equalsIgnoreCase(stateAbbr)) {
//                    countyEquivalent = Designation.Municipio;
//                } else if ("UST".equalsIgnoreCase(stateAbbr)) {
//                    countyEquivalent = Designation.Territory;
//                }
                Level2 county = new Level2(state, countyEquivalent);
                county.c_displayName = name;
                county.c_primaryKey = countyFips;
                state.getSubDivisionList().add(county);
            }
            });
        } catch (Exception ex) {
        }

        //latlongs
        // MEH-webstart read(ClassLoader.getSystemResourceAsStream("usda/weru/resources/site_latlong.txt"), new LineHandler() {
        // For Webstart, need to use the context loader, not system loader, (system loader cannot find the file in WS)
        //read(Thread.currentThread().getContextClassLoader().getResourceAsStream("usda/weru/resources/site_latlong.txt"), new LineHandler() {
        try {
        inputFile = new File (gisFile,"site_latlong.txt");
        fileStream = new FileInputStream (inputFile);
        read(fileStream, new LineHandler() {

            @Override
            public void handle(String line) {
                String[] parts = line.split("_", -1);
                String code = parts[0];

                Site&lt;?&gt; site = Site.valueOf(code);

                if (site == null) {
                    LOGGER.warn("Unknown site for latlong: " + line);
                    return;
                }

                String latString = parts[1];
                String lonString = parts[2];

                double lat = Double.valueOf(latString);
                double lon = Double.valueOf(lonString);
                LatLong latlong = LatLong.valueOf(lat, lon, USCustomary.DEGREE_ANGLE);

                site.c_latlongFallback = latlong;

            }
            });
        } catch (Exception ex) {
        }

        //hasc to fips
        // MEH-webstart read(ClassLoader.getSystemResourceAsStream("usda/weru/resources/site_hasc2fips.txt"), new LineHandler() {
        // For Webstart, need to use the context loader, not system loader, (system loader cannot find the file in WS)
        //read(Thread.currentThread().getContextClassLoader().getResourceAsStream("usda/weru/resources/site_hasc2fips.txt"), new LineHandler() {
        try {
        inputFile = new File (gisFile,"site_hasc2fips.txt");
        fileStream = new FileInputStream (inputFile);
        read(fileStream, new LineHandler() {

            @Override
            public void handle(String line) {
                String[] parts = line.split("_", -1);
                String hasc = parts[0];
                String fips = parts[1];

                Site&lt;?&gt; site = valueOfFIPS(fips);
                if (site != null) {
                    SITES_HASC.put(hasc, site);
                    site.c_hasc = hasc;
                } else {
                    LOGGER.debug("Unable to find site for HASC referenced FIPS code: " + fips);
                }

            }
        });
        } catch (Exception ex) {
        }

        Quantity&lt;Time&gt; elapsedTime = Quantities.getQuantity(System.nanoTime() - startTime, MetricPrefix.NANO(SI.SECOND));
        LOGGER.info("Finished initializing site data: "
                + DecimalFormat.getInstance().format(elapsedTime.to(SI.SECOND).getValue().doubleValue()) + " s");

    }

    private static Designation findDesignation(String text) {
        if (text == null) {
            return null;
        }
        text = text.trim();
        for (Designation type : Designation.values()) {
            for (String name : type.getAlternativeNames()) {
                if (name.toLowerCase().equals(text.toLowerCase())) {
                    return type;
                }
            }
        }
        return null;
    }

    public static class LevelComparator implements Comparator&lt;Site&lt;?&gt;&gt; {

        @Override
        public int compare(Site&lt;?&gt; a, Site&lt;?&gt; b) {
            if (isAncestor(a, b)) {
                return -1;
            } else if (isAncestor(b, a)) {
                return 1;
            } else {
                return 0;
            }
        }

        private boolean isAncestor(Site&lt;?&gt; parent, Site&lt;?&gt; child) {
            while (child != null) {
                if (parent.equals(child)) {
                    return true;
                }
                child = child.getParent();
            }
            return false;
        }
    }
    //useful constants

    public static final Site.Level0 UNITED_STATES = (Site.Level0) Site.valueOfFIPS("US00");
    public static final Site.Level0 CHINA = (Site.Level0) Site.valueOfFIPS("CH00");

    private static interface LineHandler {

        public void handle(String line);
    }

    private static void read(InputStream in, LineHandler handler) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(in));

            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
                line = line.trim();
                if (line.startsWith("#") || line.length() == 0) {
                    //skip comments or blank lines
                    continue;
                }

                //pass the work off to the line handler
                try {
                    handler.handle(line);
                } catch (Exception e) {
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}
</pre></body></html>