org.ktunaxa.referral.shapereader.ShapeReaderServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.ktunaxa.referral.shapereader.ShapeReaderServiceImpl.java

Source

/*
 * Ktunaxa Referral Management System.
 *
 * Copyright (C) see version control system
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.ktunaxa.referral.shapereader;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.List;

import org.geotools.data.DataStore;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.FeatureIterator;
import org.geotools.referencing.CRS;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.ktunaxa.referral.server.domain.ReferenceBase;
import org.ktunaxa.referral.server.domain.ReferenceLayer;
import org.ktunaxa.referral.server.domain.ReferenceValue;
import org.ktunaxa.referral.server.service.KtunaxaConstant;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.PropertyName;
import org.opengis.referencing.FactoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Implementation of the {@link ShapeReaderService} that uses a pre-configured
 * base path to search for shape files, and also uses the
 * {@link LayerPersistService} to actually persist shape files into the
 * database.
 * 
 * @author Pieter De Graef
 */
@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
@Component("shapeReaderService")
public class ShapeReaderServiceImpl implements ShapeReaderService {

    @Autowired
    private LayerPersistService persistService;

    @Autowired
    @Qualifier("postgisSessionFactory")
    private SessionFactory sessionFactory;

    private String basePath;

    private final Logger log = LoggerFactory.getLogger(ShapeReaderServiceImpl.class);

    // ------------------------------------------------------------------------
    // ShapeReaderService implementation:
    // ------------------------------------------------------------------------

    /**
     * <p>
     * Retrieve a list of available shape files to chose from. The idea is that
     * in some location a list of shape files can be retrieved, of which we want
     * to upload one.
     * </p>
     * <p>
     * This implementation uses a "base path" (system folder) to look for shape
     * files.
     * </p>
     * 
     * @param subDirectory extra path element to indicate sub-package/directory
     * @return Returns the full list of available shape files available on the
     *         configured base path.
     * @throws IOException
     *             Thrown if something goes wrong while retrieving available
     *             shape files.
     */
    public File[] getAllFiles(String subDirectory) throws IOException {
        if (basePath.startsWith("classpath:")) {
            String fullPath = basePath.substring(10);
            if (subDirectory != null && subDirectory.trim().length() > 0) {
                fullPath = fullPath + "/" + subDirectory;
            }
            PathMatchingResourcePatternResolver pmrpr = new PathMatchingResourcePatternResolver();
            Resource[] resources = pmrpr.getResources(fullPath + File.separator + "*.shp");
            int length = resources.length;
            File[] files = new File[length];
            for (int i = 0; i < length; i++) {
                files[i] = resources[i].getFile();
            }
            return files;
        } else {
            String fullPath = basePath;
            if (subDirectory != null && subDirectory.trim().length() > 0) {
                fullPath = fullPath + File.separator + subDirectory;
            }
            File folder = new File(fullPath); // We don't have to check for folder==null.
            if (!folder.isDirectory()) {
                throw new IOException(
                        "Configured base path is not a directory: " + basePath + " translated to " + fullPath);
            }
            return folder.listFiles(new FilenameFilter() {

                public boolean accept(File dir, String name) {
                    return name.endsWith(".shp");
                }
            });
        }

    }

    /**
     * Actually read a shape file (.shp) and return it in the form of a GeoTools
     * DataStore.
     * 
     * @param file
     *            The chosen shape file to read. Must be a .shp file - no
     *            compressed file!
     * @return Returns the GeoTools DataStore representation of that shape file.
     *         This DataStore can read the contents.
     * @throws IOException
     */
    public DataStore read(File file) throws IOException {
        ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
        DataStore shpStore = factory.createDataStore(new URL("file://" + file.getAbsolutePath()));
        if (shpStore == null) {
            throw new IOException("Shape file could not be properly read.");
        }
        return shpStore;
    }

    /**
     * Validate the given GeoTools DataStore to see if it's contents can be used
     * as a reference layer (base or value). For validation to succeed, 2
     * specific attributes need to be available:
     * <ul>
     * <li>RMS_LABEL</li>
     * <li>RMS_STYLE</li>
     * </ul>
     * 
     * @param dataStore
     *            The DataStore to validate.
     * @throws IOException
     *             Thrown when the DataStore does not meet the requirements.
     */
    public void validateFormat(DataStore dataStore) throws IOException {
        String[] typeNames = dataStore.getTypeNames();
        for (String typeName : typeNames) {
            SimpleFeatureType schema = dataStore.getSchema(typeName);
            Expression style = persistService.getStyleAttributeExpression(typeName);
            Expression label = persistService.getLabelAttributeExpression(typeName);
            if (style == Expression.NIL) {
                throw new IOException("Can't evaluate attribute expression");
            }
            if (label == Expression.NIL) {
                throw new IOException("Can't evaluate label expression");
            }
            if (!isValid(style, schema)) {
                throw new IOException("The attribute '" + style + "' is missing from the shape file definition.");
            }
            if (!isValid(label, schema)) {
                throw new IOException("The attribute '" + label + "' is missing from the shape file definition.");
            }
            Integer code;
            try {
                code = CRS.lookupEpsgCode(schema.getCoordinateReferenceSystem(), false);
            } catch (FactoryException fe) {
                throw new IOException(
                        "Could not look up EPSG code for coordinate reference system: " + fe.getMessage(), fe);
            }
            if (code == null) {
                // Backup plan:
                String crsName = schema.getCoordinateReferenceSystem().getName().getCode();
                if ("NAD_1983_UTM_Zone_11N".equalsIgnoreCase(crsName)) {
                    code = KtunaxaConstant.LAYER_SRID;
                } else {
                    throw new IOException("Unknown coordinate reference system: " + crsName);
                }
            }
            if (code != persistService.getSrid()) {
                throw new IOException("The shape files contains the wrong coordinate reference system. EPSG"
                        + persistService.getSrid() + " was expected.");
            }
        }
    }

    /**
     * Get the full list of known reference layers (both base and value).
     * 
     * @return The full list of known reference layers (both base and value).
     */
    @SuppressWarnings("unchecked")
    public List<ReferenceLayer> getAllLayers() {
        Session session = sessionFactory.getCurrentSession();
        return (List<ReferenceLayer>) session.createCriteria(ReferenceLayer.class).list();
    }

    /**
     * <p>
     * Persist the given DataStore as the new contents for the given layer into
     * the database, replacing the old contents.
     * </p>
     * <p>
     * This method make use of the {@link LayerPersistService} to do the actual
     * work. It simply makes sure that everything happens within a single
     * transaction.
     * </p>
     * 
     * @param layer
     *            The reference layer to replace the contents in.
     * @param dataStore
     *            The data source containing the new contents.
     * @throws IOException
     *             Thrown when any error occurs within the process. This should
     *             automatically roll back the database to the original state.
     */
    public void persist(ReferenceLayer layer, DataStore dataStore) throws IOException {
        persistService.clearLayer(layer);

        String[] typeNames = dataStore.getTypeNames();
        FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeNames[0]);
        FeatureIterator<SimpleFeature> iterator = source.getFeatures().features();
        int i = 0;
        while (iterator.hasNext()) {
            SimpleFeature feature = iterator.next();
            persistService.validate(feature);

            if (layer.getType().isBaseLayer()) {
                ReferenceBase reference = persistService.convertToBase(layer, feature);
                persistService.persist(reference);
            } else {
                ReferenceValue reference = persistService.convertToValue(layer, feature);
                persistService.persist(reference);
            }
            if (++i % 50 == 0) {
                persistService.flushSession();
            }
            if (i % 1000 == 0) {
                log.info(i + " objects persisted.");
            }
        }
    }

    // ------------------------------------------------------------------------
    // Getters and setters:
    // ------------------------------------------------------------------------

    /**
     * Set the folder that determines where this service should go looking for
     * shape files.
     * 
     * @param basePath
     *            The new shape file base directory.
     */
    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    private boolean isValid(Expression expression, SimpleFeatureType schema) {
        // can only validate if property
        if (expression instanceof PropertyName) {
            PropertyName prop = (PropertyName) expression;
            return schema.getType(prop.getPropertyName()) != null;
        }
        return true;
    }
}