au.org.ala.layers.intersect.IntersectConfig.java Source code

Java tutorial

Introduction

Here is the source code for au.org.ala.layers.intersect.IntersectConfig.java

Source

/**************************************************************************
 * Copyright (C) 2010 Atlas of Living Australia
 * All Rights Reserved.
 * <p>
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * <p>
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 ***************************************************************************/
package au.org.ala.layers.intersect;

import au.org.ala.layers.dao.FieldDAO;
import au.org.ala.layers.dao.LayerDAO;
import au.org.ala.layers.dto.Field;
import au.org.ala.layers.dto.GridClass;
import au.org.ala.layers.dto.IntersectionFile;
import au.org.ala.layers.dto.Layer;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author Adam
 */
public class IntersectConfig {

    public static final String GEOSERVER_URL_PLACEHOLDER = "<COMMON_GEOSERVER_URL>";
    public static final String GEONETWORK_URL_PLACEHOLDER = "<COMMON_GEONETWORK_URL>";
    static final String ALASPATIAL_OUTPUT_PATH = "ALASPATIAL_OUTPUT_PATH";
    static final String LAYER_FILES_PATH = "LAYER_FILES_PATH";
    static final String ANALYSIS_LAYER_FILES_PATH = "ANALYSIS_LAYER_FILES_PATH";
    static final String ANALYSIS_TMP_LAYER_FILES_PATH = "ANALYSIS_TMP_LAYER_FILES_PATH";
    static final String LAYER_INDEX_URL = "LAYER_INDEX_URL";
    static final String BATCH_THREAD_COUNT = "BATCH_THREAD_COUNT";
    static final String CONFIG_RELOAD_WAIT = "CONFIG_RELOAD_WAIT";
    static final String PRELOADED_SHAPE_FILES = "PRELOADED_SHAPE_FILES";
    static final String GRID_BUFFER_SIZE = "GRID_BUFFER_SIZE";
    static final String GRID_CACHE_PATH = "GRID_CACHE_PATH";
    static final String GRID_CACHE_READER_COUNT = "GRID_CACHE_READER_COUNT";
    static final String LOCAL_SAMPLING = "LOCAL_SAMPLING";
    static final String GEOSERVER_URL = "GEOSERVER_URL";
    static final String GEONETWORK_URL = "GEONETWORK_URL";
    static final String GDAL_PATH = "GDAL_PATH";
    static final String ANALYSIS_RESOLUTIONS = "ANALYSIS_RESOLUTIONS";
    static final String OCCURRENCE_SPECIES_RECORDS_FILENAME = "OCCURRENCE_SPECIES_RECORDS_FILENAME";
    static final String UPLOADED_SHAPES_FIELD_ID = "UPLOADED_SHAPES_FIELD_ID";
    static final String API_KEY_CHECK_URL_TEMPLATE = "API_KEY_CHECK_URL_TEMPLATE";
    static final String SPATIAL_PORTAL_APP_NAME = "SPATIAL_PORTAL_APP_NAME";
    static final String BIOCACHE_SERVICE_URL = "BIOCACHE_SERVICE_URL";
    static final String GEOSERVER_USERNAME = "GEOSERVER_USERNAME";
    static final String GEOSERVER_PASSWORD = "GEOSERVER_PASSWORD";
    static final String SHP2PGSQL_PATH = "SHP2PGSQL_PATH";
    static final String GRIDS_TO_CACHE = "GRIDS_TO_CACHE";
    static final String CAN_UPDATE_LAYER_DISTANCES = "CAN_UPDATE_LAYER_DISTANCES";
    static final String CAN_UPDATE_GRID_CACHE = "CAN_UPDATE_GRID_CACHE";
    static final String CAN_GENERATE_ANALYSIS_FILES = "CAN_GENERATE_ANALYSIS_FILES";
    static final String CAN_INTERSECT_LAYERS = "CAN_INTERSECT_LAYERS";
    static final String CAN_GENRATE_THUMBNAILS = "CAN_GENRATE_THUMBNAILS";
    /**
     * log4j logger
     */
    private static final Logger logger = Logger.getLogger(IntersectConfig.class);
    static ObjectMapper mapper = new ObjectMapper();
    static String layerFilesPath;
    static String analysisLayerFilesPath;
    static String analysisTmpLayerFilesPath;
    static String alaspatialOutputPath;
    static String layerIndexUrl;
    static int batchThreadCount;
    static long configReloadWait;
    static String preloadedShapeFiles;
    static int gridBufferSize;
    static String gridCachePath;
    static int gridCacheReaderCount;
    static boolean localSampling;
    static String geoserverUrl;
    static String geonetworkUrl;
    static String gdalPath;
    static List<Double> analysisResolutions;
    static String occurrenceSpeciesRecordsFilename;
    static String uploadedShapesFieldId;
    static String apiKeyCheckUrlTemplate;
    static String spatialPortalAppName;
    static String biocacheServiceUrl;
    static String geoserverUsername;
    static String geoserverPassword;
    static String shp2pgsqlPath;
    static boolean fieldStyles;
    static private boolean canUpdateLayerDistances;
    static private boolean canUpdateGridCache;
    static private boolean canGenerateAnalysisLayers;
    static private boolean canIntersectLayers;
    static private boolean canGenerateThumbnails;

    static {
        Properties properties = new Properties();
        InputStream is = null;
        try {
            String pth = "/data/layers-store/config/layers-store-config.properties";
            logger.debug("config path: " + pth);
            is = new FileInputStream(pth);
            if (is != null) {
                properties.load(is);
            } else {
                String msg = "cannot get properties file: " + pth;
                logger.warn(msg);
            }
        } catch (IOException ex) {
            logger.error(null, ex);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    logger.error("failed to close layers-store-config.properties", e);
                }
            }
        }

        layerFilesPath = getProperty(LAYER_FILES_PATH, properties, null);
        isValidPath(layerFilesPath, LAYER_FILES_PATH);
        analysisLayerFilesPath = getProperty(ANALYSIS_LAYER_FILES_PATH, properties, null);
        isValidPath(analysisLayerFilesPath, ANALYSIS_LAYER_FILES_PATH);
        analysisTmpLayerFilesPath = getProperty(ANALYSIS_TMP_LAYER_FILES_PATH, properties, null);
        isValidPath(analysisTmpLayerFilesPath, ANALYSIS_TMP_LAYER_FILES_PATH);
        alaspatialOutputPath = getProperty(ALASPATIAL_OUTPUT_PATH, properties, null);
        isValidPath(alaspatialOutputPath, ALASPATIAL_OUTPUT_PATH);
        layerIndexUrl = getProperty(LAYER_INDEX_URL, properties, null);
        isValidUrl(layerIndexUrl, LAYER_INDEX_URL);
        batchThreadCount = (int) getPositiveLongProperty(BATCH_THREAD_COUNT, properties, 1);
        configReloadWait = getPositiveLongProperty(CONFIG_RELOAD_WAIT, properties, 3600000);
        preloadedShapeFiles = getProperty(PRELOADED_SHAPE_FILES, properties, null);
        gridBufferSize = (int) getPositiveLongProperty(GRID_BUFFER_SIZE, properties, 4096);
        gridCachePath = getProperty(GRID_CACHE_PATH, properties, null);
        gridCacheReaderCount = (int) getPositiveLongProperty(GRID_CACHE_READER_COUNT, properties, 10);
        localSampling = getProperty(LOCAL_SAMPLING, properties, "true").toLowerCase().equals("true");
        geoserverUrl = getProperty(GEOSERVER_URL, properties, null);
        geoserverUsername = getProperty(GEOSERVER_USERNAME, properties, null);
        geoserverPassword = getProperty(GEOSERVER_PASSWORD, properties, null);

        geonetworkUrl = getProperty(GEONETWORK_URL, properties, null);

        gdalPath = getProperty(GDAL_PATH, properties, null);
        isValidPathGDAL(gdalPath, GDAL_PATH);
        analysisResolutions = getDoublesFrom(getProperty(ANALYSIS_RESOLUTIONS, properties, "0.5"));
        occurrenceSpeciesRecordsFilename = getProperty(OCCURRENCE_SPECIES_RECORDS_FILENAME, properties, null);
        uploadedShapesFieldId = getProperty(UPLOADED_SHAPES_FIELD_ID, properties, null);
        apiKeyCheckUrlTemplate = getProperty(API_KEY_CHECK_URL_TEMPLATE, properties, null);

        spatialPortalAppName = getProperty(SPATIAL_PORTAL_APP_NAME, properties, null);

        biocacheServiceUrl = getProperty(BIOCACHE_SERVICE_URL, properties, null);

        canGenerateAnalysisLayers = getProperty(CAN_GENERATE_ANALYSIS_FILES, properties, "false")
                .equalsIgnoreCase("true");
        canGenerateThumbnails = getProperty(CAN_GENRATE_THUMBNAILS, properties, "false").equalsIgnoreCase("true");
        canIntersectLayers = getProperty(CAN_INTERSECT_LAYERS, properties, "false").equalsIgnoreCase("true");
        canUpdateGridCache = getProperty(CAN_UPDATE_GRID_CACHE, properties, "false").equalsIgnoreCase("true");
        canUpdateLayerDistances = getProperty(CAN_UPDATE_LAYER_DISTANCES, properties, "false")
                .equalsIgnoreCase("true");

        String gridsToCache = getProperty(GRIDS_TO_CACHE, properties, "1");
        if ("all".equals(gridsToCache)) {
            Grid.maxGridsLoaded = -1;
        } else {
            try {
                Grid.maxGridsLoaded = Integer.parseInt(gridsToCache);
            } catch (Exception e) {
                logger.error("failed to parse 'GRIDS_TO_CACHE' property as Integer: " + gridsToCache, e);
            }
        }

        shp2pgsqlPath = getProperty(SHP2PGSQL_PATH, properties, null);
    }

    long lastReload;
    SimpleShapeFileCache shapeFileCache;
    HashMap<String, IntersectionFile> intersectionFiles;
    HashMap<String, HashMap<Integer, GridClass>> classGrids;
    private FieldDAO fieldDao;
    private LayerDAO layerDao;

    public IntersectConfig(FieldDAO fieldDao, LayerDAO layerDao) {
        this.fieldDao = fieldDao;
        this.layerDao = layerDao;
        load();
    }

    private static void isValidPath(String path, String desc) {
        File f = new File(path);

        if (!f.exists()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  It does not exist.");
        } else if (!f.isDirectory()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  It is not a directory.");
        } else if (!f.canRead()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  Not permitted to READ.");
        } else if (!f.canWrite()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  Not permitted to WRITE.");
        }

    }

    private static void isValidPathGDAL(String path, String desc) {
        File f = new File(path);

        if (!f.exists()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\" is not a valid local file path.  It does not exist.");
        } else if (!f.isDirectory()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  It is not a directory.");
        } else if (!f.canRead()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  Not permitted to READ.");
        }

        //look for GDAL file "gdalwarp"
        File g = new File(path + File.separator + "gdalwarp");
        if (!f.exists()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  gdalwarp does not exist.");
        } else if (!g.canExecute()) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + path
                    + "\"  is not a valid local file path.  gdalwarp not permitted to EXECUTE.");
        }
    }

    private static void isValidUrl(String url, String desc) {
        HttpClient client = new HttpClient();
        GetMethod get = null;

        try {
            get = new GetMethod(url);
            int result = client.executeMethod(get);

            if (result != 200) {
                logger.error("Config error. Property \"" + desc + "\" with value \"" + url
                        + "\"  is not a valid URL.  Error executing GET request, response=" + result);
            }
        } catch (Exception e) {
            logger.error("Config error. Property \"" + desc + "\" with value \"" + url
                    + "\"  is not a valid URL.  Error executing GET request.");
        } finally {
            if (get != null) {
                get.releaseConnection();
            }
        }

    }

    public static void setPreloadedShapeFiles(String preloadedShapeFiles) {
        IntersectConfig.preloadedShapeFiles = preloadedShapeFiles;
    }

    static String getProperty(String property, Properties properties, String defaultValue) {
        String p = System.getProperty(property);
        if (p == null) {
            p = properties.getProperty(property);
        }
        if (p == null) {
            p = defaultValue;
        }
        logger.info(property + " > " + p);
        return p;
    }

    static long getPositiveLongProperty(String property, Properties properties, long defaultValue) {
        String p = getProperty(property, properties, null);
        long l = defaultValue;
        try {
            l = Long.parseLong(p);
            if (l < 0) {
                l = defaultValue;
            }
        } catch (NumberFormatException ex) {
            logger.error("parsing " + property + ": " + p + ", using default: " + defaultValue, ex);
        }
        return l;
    }

    static public String getAlaspatialOutputPath() {
        return alaspatialOutputPath;
    }

    static public String getLayerFilesPath() {
        return layerFilesPath;
    }

    static public String getLayerIndexUrl() {
        return layerIndexUrl;
    }

    static public int getThreadCount() {
        return batchThreadCount;
    }

    static public int getGridBufferSize() {
        return gridBufferSize;
    }

    static public String getGridCachePath() {
        return gridCachePath;
    }

    static public int getGridCacheReaderCount() {
        return gridCacheReaderCount;
    }

    static private HashMap<Integer, GridClass> getGridClasses(String filePath, String type) throws IOException {
        HashMap<Integer, GridClass> classes = null;
        if (type.equals("Contextual")) {
            if (new File(filePath + ".gri").exists() && new File(filePath + ".grd").exists()
                    && new File(filePath + ".txt").exists()) {
                File gridClassesFile = new File(filePath + ".classes.json");
                if (gridClassesFile.exists()) {
                    classes = mapper.readValue(gridClassesFile, new TypeReference<Map<Integer, GridClass>>() {
                    });
                    logger.info("found grid classes for " + gridClassesFile.getPath());
                } else {
                    logger.error(
                            "classes unavailable for " + gridClassesFile.getPath() + ", build classes offline");
                    //                logger.info("building " + gridClassesFile.getPath());
                    //                long start = System.currentTimeMillis();
                    //                classes = GridClassBuilder.buildFromGrid(filePath);
                    //                logger.info("finished building " + gridClassesFile.getPath() + " in " + (System.currentTimeMillis() - start) + " ms");
                }
            } else if (new File(filePath + ".gri").exists() && new File(filePath + ".grd").exists()) {
                logger.error("missing grid classes for " + filePath);
            }
        } else {

        }
        return classes;
    }

    static public long getConfigReloadWait() {
        return configReloadWait;
    }

    static public String getGeoserverUrl() {
        return geoserverUrl;
    }

    static public String getGeonetworkUrl() {
        return geonetworkUrl;
    }

    static public String getAnalysisLayerFilesPath() {
        return analysisLayerFilesPath;
    }

    static public String getGdalPath() {
        return gdalPath;
    }

    static public List<Double> getAnalysisResolutions() {
        return analysisResolutions;
    }

    static private List<Double> getDoublesFrom(String property) {
        List<Double> l = new ArrayList<Double>();
        if (property != null) {
            for (String s : property.split(",")) {
                try {
                    Double d = Double.parseDouble(s.trim());
                    if (d != null && !d.isNaN()) {
                        l.add(d);
                    } else {
                        logger.warn("Cannot parse '" + s + "' to Double");
                    }
                } catch (Exception e) {
                    logger.warn("Cannot parse '" + s + "' to Double", e);
                }
            }
        }
        java.util.Collections.sort(l);
        return l;
    }

    static public String getOccurrenceSpeciesRecordsFilename() {
        return occurrenceSpeciesRecordsFilename;
    }

    static public String getUploadedShapesFieldId() {
        return uploadedShapesFieldId;
    }

    static public String getApiKeyCheckUrlTemplate() {
        return apiKeyCheckUrlTemplate;
    }

    static public String getSpatialPortalAppName() {
        return spatialPortalAppName;
    }

    public static String getAnalysisTmpLayerFilesPath() {
        return analysisTmpLayerFilesPath;
    }

    public static int getMaxGridsLoaded() {
        return Grid.maxGridsLoaded;
    }

    public static void setMaxGridsLoaded(int maxGridsLoaded) {
        Grid.maxGridsLoaded = maxGridsLoaded;
    }

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < 100000; i++) {
            if (i > 0) {
                sb.append(',');
            }
            sb.append(Math.random() * (-12 + 44) - 44).append(',').append(Math.random() * (154 - 112) + 112);
        }
        try {
            FileUtils.writeStringToFile(new File("/data/p.txt"), sb.toString());
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }

    static public boolean getCanUpdateLayerDistances() {
        return canUpdateLayerDistances;
    }

    static public boolean getCanUpdateGridCache() {
        return canUpdateGridCache;
    }

    static public boolean getCanGenerateAnalysisLayers() {
        return canGenerateAnalysisLayers;
    }

    static public boolean getCanIntersectLayers() {
        return canIntersectLayers;
    }

    static public boolean getCanGenerateThumbnails() {
        return canGenerateThumbnails;
    }

    static public boolean hasFieldStyles() {
        return fieldStyles;
    }

    public String getBiocacheServiceUrl() {
        return biocacheServiceUrl;
    }

    public void load() {
        lastReload = System.currentTimeMillis();

        try {
            updateIntersectionFiles();
            updateShapeFileCache();

            logger.info("**** grids to cache ***** = " + Grid.maxGridsLoaded);
            if (Grid.maxGridsLoaded <= 0) {
                seedGridFileCache();
            }
        } catch (Exception e) {
            //if it fails, set reload wait low
            logger.error("load failed, retry in 30s", e);
            configReloadWait = 30000;
        }
    }

    private void seedGridFileCache() {
        int count = 0;
        for (String s : intersectionFiles.keySet()) {
            if (s.startsWith("el") && intersectionFiles.get(s).getType().equalsIgnoreCase("environmental")) {
                try {
                    Grid g = Grid.getGrid(intersectionFiles.get(s).getFilePath());
                    g.getGrid();
                } catch (Exception e) {
                    logger.info("error caching grid: " + s);
                }
                count++;
                if (count % 5 == 0) {
                    logger.info("cached " + count + " grids");
                }
            }
        }
    }

    public IntersectionFile getIntersectionFile(String fieldId) {
        return intersectionFiles.get(fieldId);
    }

    public String getFieldIdFromFile(String file) {
        String off, on;
        if (File.separator.equals("/")) {
            off = "\\";
            on = "/";
        } else {
            on = "\\";
            off = "/";
        }
        file = file.replace(off, on);
        for (Entry<String, IntersectionFile> entry : intersectionFiles.entrySet()) {
            if (entry.getValue().getFilePath().replace(off, on).equalsIgnoreCase(file)) {
                return entry.getKey();
            }
        }
        return file;
    }

    private void updateIntersectionFiles() throws MalformedURLException, IOException {
        if (intersectionFiles == null) {
            intersectionFiles = new HashMap<String, IntersectionFile>();
            classGrids = new HashMap<String, HashMap<Integer, GridClass>>();
        }

        if (layerIndexUrl != null) {
            //request from url
            JSONArray layers = JSONArray.fromObject(getUrl(layerIndexUrl + "/layers"));
            HashMap<String, String> layerPathOrig = new HashMap<String, String>();
            HashMap<String, String> layerName = new HashMap<String, String>();
            HashMap<String, String> layerType = new HashMap<String, String>();
            HashMap<String, String> layerPid = new HashMap<String, String>();
            for (int i = 0; i < layers.size(); i++) {
                layerPathOrig.put(layers.getJSONObject(i).getString("id"),
                        layers.getJSONObject(i).getString("path_orig"));
                layerName.put(layers.getJSONObject(i).getString("id"), layers.getJSONObject(i).getString("name"));
                layerType.put(layers.getJSONObject(i).getString("id"), layers.getJSONObject(i).getString("type"));
                layerPid.put(layers.getJSONObject(i).getString("id"), layers.getJSONObject(i).getString("id"));
            }

            JSONArray fields = JSONArray.fromObject(getUrl(layerIndexUrl + "/fields"));
            for (int i = 0; i < fields.size(); i++) {
                JSONObject jo = fields.getJSONObject(i);
                String spid = jo.getString("spid");
                if (layerPathOrig.get(spid) == null) {
                    logger.error("cannot find layer with id '" + spid + "'");
                    continue;
                }
                HashMap<Integer, GridClass> gridClasses = getGridClasses(layerFilesPath + layerPathOrig.get(spid),
                        layerType.get(spid));

                IntersectionFile intersectionFile = new IntersectionFile(jo.getString("name"),
                        layerFilesPath + layerPathOrig.get(spid),
                        (jo.containsKey("sname") ? jo.getString("sname") : null),
                        layerName.get(jo.getString("spid")), jo.getString("id"), jo.getString("name"),
                        layerPid.get(spid), jo.getString("type"), gridClasses);

                intersectionFiles.put(jo.getString("id"), intersectionFile);
                //also register it under the layer name
                intersectionFiles.put(layerName.get(spid), intersectionFile);
                //also register it under the layer pid
                intersectionFiles.put(layerPid.get(spid), intersectionFile);
                classGrids.put(jo.getString("id"), gridClasses);
            }
        } else {
            for (Field f : fieldDao.getFields()) {
                if (f.isEnabled()) {
                    Layer layer = layerDao.getLayerById(Integer.parseInt(f.getSpid()), false);
                    if (layer == null) {
                        logger.error("cannot find layer with id '" + f.getSpid() + "'");
                        continue;
                    }
                    HashMap<Integer, GridClass> gridClasses = getGridClasses(
                            getLayerFilesPath() + layer.getPath_orig(), layer.getType());
                    IntersectionFile intersectionFile = new IntersectionFile(f.getName(),
                            getLayerFilesPath() + layer.getPath_orig(), f.getSname(), layer.getName(), f.getId(),
                            f.getName(), String.valueOf(layer.getId()), f.getType(), gridClasses);

                    intersectionFiles.put(f.getId(), intersectionFile);
                    //also register it under the layer name
                    //- only if default layer not already added
                    if (f.isDefaultlayer() || intersectionFiles.get(layer.getName()) == null) {
                        intersectionFiles.put(layer.getName(), intersectionFile);
                    }
                    //also register it under the layer pid
                    if (f.isDefaultlayer() || intersectionFiles.get(String.valueOf(layer.getId())) == null) {
                        intersectionFiles.put(String.valueOf(layer.getId()), intersectionFile);
                    }
                    classGrids.put(f.getId(), gridClasses);
                }
            }
        }
    }

    String getUrl(String url) {
        HttpClient client = new HttpClient();
        GetMethod get = null;

        try {
            logger.info("opening url: " + url);
            get = new GetMethod(url);

            int result = client.executeMethod(get);
            String slist = get.getResponseBodyAsString();
            return slist;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (get != null) {
                get.releaseConnection();
            }
        }
        return "";
    }

    public void updateShapeFileCache() {
        if (preloadedShapeFiles == null || preloadedShapeFiles.length() == 0) {
            return;
        }

        String[] fields = preloadedShapeFiles.split(",");

        //requires readLayerInfo() first
        String[] layers;
        String[] columns;
        String[] fid;

        Set shapefiles = new HashSet();
        Set<String> fieldSet = new HashSet<String>();
        if (fields[0].equalsIgnoreCase("all")) {
            for (String s : intersectionFiles.keySet()) {
                if (s.startsWith("cl") && intersectionFiles.get(s).getType().equalsIgnoreCase("c")) {
                    fieldSet.add(s);
                    shapefiles.add(intersectionFiles.get(s).getFilePath());
                }
            }
        } else {
            for (String s : fields) {
                fieldSet.add(s);
                shapefiles.add(intersectionFiles.get(s).getFilePath());
            }
            //must include other fields that use the same shapefile
            for (String s : intersectionFiles.keySet()) {
                if (s.startsWith("cl") && intersectionFiles.get(s).getType().equalsIgnoreCase("c")
                        && shapefiles.contains(intersectionFiles.get(s).getFilePath()) && !fieldSet.contains(s)) {
                    fieldSet.add(s);
                }
            }
        }
        layers = new String[shapefiles.size()];
        columns = new String[shapefiles.size()];
        fid = new String[shapefiles.size()];
        int i = 0;
        Map<String, Integer> addedShapefiles = new HashMap<String, Integer>();
        for (String s : fieldSet) {
            String path = intersectionFiles.get(s).getFilePath();

            IntersectionFile f = intersectionFiles.get(s);

            if (addedShapefiles.containsKey(path)) {
                //already added, append shape fields
                Integer pos = addedShapefiles.get(path);
                columns[pos] += "," + f.getShapeFields();
                fid[pos] += "," + f.getFieldId();
            } else {
                layers[i] = f.getFilePath();
                columns[i] = f.getShapeFields();
                fid[i] = f.getFieldId();
                addedShapefiles.put(path, i);
                i++;
            }
        }

        if (shapeFileCache == null) {
            shapeFileCache = new SimpleShapeFileCache(layers, columns, fid);
        } else {
            shapeFileCache.update(layers, columns, fid);
        }
    }

    /**
     * Add shape files to the shape file cache.
     *
     * @param fieldIds comma separated fieldIds.  Must be cl fields.
     */
    public void addToShapeFileCache(String fieldIds) {
        if (preloadedShapeFiles != null) {
            fieldIds += "," + preloadedShapeFiles;
        }
        String[] fields = fieldIds.split(",");

        //requres readLayerInfo() first
        String[] layers = new String[fields.length];
        String[] columns = new String[fields.length];
        String[] fid = new String[fields.length];

        int pos = 0;
        for (int i = 0; i < fields.length; i++) {
            try {
                layers[pos] = getIntersectionFile(fields[i].trim()).getFilePath();
                columns[pos] = getIntersectionFile(fields[i].trim()).getShapeFields();
                fid[pos] = fields[i];
                pos++;
            } catch (Exception e) {
                logger.error("problem adding shape file to cache for field: " + fields[i], e);
            }
        }
        if (pos < layers.length) {
            layers = java.util.Arrays.copyOf(layers, pos);
            columns = java.util.Arrays.copyOf(columns, pos);
            fid = java.util.Arrays.copyOf(fid, pos);
        }

        if (shapeFileCache == null) {
            shapeFileCache = new SimpleShapeFileCache(layers, columns, fid);
        } else {
            shapeFileCache.update(layers, columns, fid);
        }
    }

    public SimpleShapeFileCache getShapeFileCache() {
        return shapeFileCache;
    }

    public boolean requiresReload() {
        return lastReload + configReloadWait >= System.currentTimeMillis();
    }

    public boolean isLocalSampling() {
        return localSampling;
    }

    public List<Field> getFieldsByDB() {
        List<Field> fields = new ArrayList<Field>();
        if (layerIndexUrl != null) {
            try {
                //request from url
                fields = mapper.readValue(getUrl(layerIndexUrl + "/fieldsdb"), new TypeReference<List<Field>>() {
                });
            } catch (Exception ex) {
                logger.error("failed to read: " + layerIndexUrl + "/fieldsdb", ex);
            }
        }
        return fields;
    }

    public Map<String, IntersectionFile> getIntersectionFiles() {
        return intersectionFiles;
    }

    /**
     * get info on an analysis layer
     *
     * @param id layer id as String
     * @return String [] with [0] = analysis id, [1] = path to grid file, [2] = analysis type
     */
    public String[] getAnalysisLayerInfo(String id) {
        String gid, filename, name;
        gid = filename = name = null;
        if (id.startsWith("species_")) {
            //maxent layer
            gid = id.substring("species_".length());
            filename = getAlaspatialOutputPath() + File.separator + "maxent" + File.separator + gid + File.separator
                    + gid;
            name = "Prediction";
        } else if (id.startsWith("aloc_")) {
            //aloc layer
            gid = id.substring("aloc_".length());
            filename = getAlaspatialOutputPath() + File.separator + "aloc" + File.separator + gid + File.separator
                    + "aloc";
            name = "Classification";
        } else if (id.startsWith("odensity_")) {
            //occurrence density layer
            gid = id.substring("odensity_".length());
            filename = getAlaspatialOutputPath() + File.separator + "sitesbyspecies" + File.separator + gid
                    + File.separator + "occurrence_density";
            name = "Occurrence Density";
        } else if (id.startsWith("srichness_")) {
            //species richness layer
            gid = id.substring("srichness_".length());
            filename = getAlaspatialOutputPath() + File.separator + "sitesbyspecies" + File.separator + gid
                    + File.separator + "species_richness";
            name = "Species Richness";
        } else if (id.endsWith("_odensity") && id.indexOf("_") == id.length() - 9) {
            //occurrence density layer and not of the form GDM's number_number_odensity
            gid = id.substring(0, id.length() - "_odensity".length());
            filename = getAlaspatialOutputPath() + File.separator + "sitesbyspecies" + File.separator + gid
                    + File.separator + "occurrence_density";
            name = "Occurrence Density";
        } else if (id.endsWith("_srichness") && id.indexOf("_") == id.length() - 10) {
            //species richness layer and not of the form GDM's number_number_srichness
            gid = id.substring(0, id.length() - "_srichness".length());
            filename = getAlaspatialOutputPath() + File.separator + "sitesbyspecies" + File.separator + gid
                    + File.separator + "species_richness";
            name = "Species Richness";
        } else if (id.startsWith("envelope_")) {
            //envelope layer
            gid = id.substring("envelope_".length());
            filename = getAlaspatialOutputPath() + File.separator + "envelope" + File.separator + gid
                    + File.separator + "envelope";
            name = "Environmental Envelope";
        } else if (id.startsWith("gdm_")) {
            //gdm layer
            int pos1 = id.indexOf("_");
            int pos2 = id.lastIndexOf("_");
            String[] gdmparts = new String[] { id.substring(0, pos1), id.substring(pos1 + 1, pos2),
                    id.substring(pos2 + 1) };
            gid = gdmparts[2];
            filename = getAlaspatialOutputPath() + File.separator + "gdm" + File.separator + gid + File.separator
                    + gdmparts[1];
            IntersectionFile f = getIntersectionFile(gdmparts[1].replaceAll("Tran", ""));
            name = "Transformed " + (f != null ? f.getFieldName() : gdmparts[1].replaceAll("Tran", ""));
        } else if (id.contains("_")) {
            //2nd form of gdm layer name, why?
            int pos = id.indexOf("_");
            String[] gdmparts = new String[] { id.substring(0, pos), id.substring(pos + 1) };
            gid = gdmparts[0];
            filename = getAlaspatialOutputPath() + File.separator + "gdm" + File.separator + gid + File.separator
                    + gdmparts[1] + "Tran";
            logger.error("id: " + id);
            logger.error("parts: " + gdmparts[0] + ", " + gdmparts[1]);
            logger.info("parts: " + gdmparts[0] + ", " + gdmparts[1]);
            logger.error("filename: " + filename);
            IntersectionFile f = getIntersectionFile(gdmparts[1]);
            name = "Transformed " + (f != null ? f.getFieldName() : gdmparts[1]);
        }

        if (gid != null) {
            return new String[] { gid, filename, name };
        } else {
            return null;
        }
    }

    public String getGeoserverUsername() {
        return geoserverUsername;
    }

    public String getGeoserverPassword() {
        return geoserverPassword;
    }

    public String getShp2pgsqlPath() {
        return shp2pgsqlPath;
    }
}