//$HeadURL$ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2008 by: Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.igeo.dataadapter; import static org.deegree.crs.coordinatesystems.GeographicCRS.WGS84; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; import java.util.UUID; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.GeometryUtils; import org.deegree.igeo.i18n.Messages; import org.deegree.igeo.io.FileSystemAccess; import org.deegree.igeo.io.FileSystemAccessFactory; import org.deegree.igeo.mapmodel.Datasource; import org.deegree.igeo.mapmodel.FileDatasource; import org.deegree.igeo.mapmodel.Layer; import org.deegree.igeo.mapmodel.MapModel; import org.deegree.igeo.views.DialogFactory; import org.deegree.igeo.views.swing.util.GenericFileChooser.FILECHOOSERTYPE; import org.deegree.io.dbaseapi.DBaseException; import org.deegree.io.gpx.GPXReader; import org.deegree.io.shpapi.ShapeFile; import org.deegree.io.shpapi.shape_new.ShapeFileWriter; import org.deegree.model.crs.CRSFactory; import org.deegree.model.crs.GeoTransformer; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureCollection; import org.deegree.model.feature.FeatureException; import org.deegree.model.feature.FeatureFactory; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.GMLFeatureAdapter; import org.deegree.model.feature.GMLFeatureCollectionDocument; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.Geometry; import org.deegree.model.spatialschema.GeometryException; import org.deegree.model.spatialschema.GeometryImpl; import org.deegree.model.spatialschema.MultiSurface; import org.deegree.model.spatialschema.Surface; import org.xml.sax.SAXException; /** * concrete implementation of {@link FeatureAdapter} for reading vector data from files. * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version. $Revision$, $Date$ */ public class FileFeatureAdapter extends FeatureAdapter { private static final ILogger LOG = LoggerFactory.getLogger( FileFeatureAdapter.class ); private boolean isLazyLoading; private Envelope lastEnv; /** * * @param module * @param datasource * @param layer * @param mapModel * @param file * @param isLazyLoading */ FileFeatureAdapter( Datasource datasource, Layer layer, MapModel mapModel, boolean isLazyLoading ) { super( datasource, layer, mapModel ); this.isLazyLoading = isLazyLoading; refresh(); loadSchema(); } /** * loads data into internal feature collection * */ private void loadData() { LOG.logDebug( "try loading: ", ( (FileDatasource) datasource ).getFile() ); String nm = ( (FileDatasource) datasource ).getFile().getName().toLowerCase(); if ( nm.endsWith( ".shp" ) ) { loadShapeFile(); } else if ( nm.endsWith( ".gml" ) || nm.endsWith( ".xml" ) ) { loadGMLFile(); } else if ( nm.endsWith( ".gpx" ) ) { loadGPXFile(); } FeatureCollection fc = featureCollections.get( datasource.getName() ); fc = ensureClockwiseSurfaceOrientation( fc ); featureCollections.put( datasource.getName(), fc ); LOG.logDebug( featureCollections.get( datasource.getName() ).size() + " features has been loaded" ); } /** * @param fc * @return */ private FeatureCollection ensureClockwiseSurfaceOrientation( FeatureCollection fc ) { int c = fc.size(); for ( int i = 0; i < c; i++ ) { Feature feature = fc.getFeature( i ); FeatureProperty[] fp = feature.getProperties(); for ( int j = 0; j < fp.length; j++ ) { Object value = fp[j].getValue(); if ( value != null && ( value instanceof Surface || value instanceof MultiSurface ) ) { try { fp[j].setValue( GeometryUtils.ensureClockwise( (Geometry) fp[j].getValue() ) ); } catch ( GeometryException e ) { e.printStackTrace(); } } } } return fc; } /** * loads the GML application schema assigned with the feature types of a WFS datasource * */ private void loadSchema() { if ( schemas.get( datasource.getName() ) == null ) { // to enable lazy loading for shape data sources and avoid reading complete file // just to get its feature type definition, read first feature from file FeatureType ft = null; File file = ( (FileDatasource) datasource ).getFile(); file = getAbsoluteFilePath( file ); String nm = file.getName().toLowerCase(); if ( nm.endsWith( ".shp" ) ) { int p = file.getAbsolutePath().lastIndexOf( '.' ); ShapeFile sf = null; try { sf = new ShapeFile( file.getAbsolutePath().substring( 0, p ) ); ft = sf.getFeatureByRecNo( 1 ).getFeatureType(); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10009", file.getAbsolutePath(), e.getMessage() ) ); } finally { if ( sf != null ) { sf.close(); } } } else if ( nm.endsWith( ".gml" ) || nm.endsWith( ".xml" ) ) { if ( featureCollections.get( datasource.getName() ) == null ) { // lazy loading is not supported for GML files because they are not indexed loadGMLFile(); } ft = featureCollections.get( datasource.getName() ).getFeature( 0 ).getFeatureType(); } else if ( nm.endsWith( ".gpx" ) ) { // lazy loading is not supported for GPX files because they are not indexed if ( featureCollections.get( datasource.getName() ) == null ) { loadGPXFile(); } ft = featureCollections.get( datasource.getName() ).getFeature( 0 ).getFeatureType(); } schemas.put( datasource.getName(), ft ); } } private void loadGPXFile() { File file = ( (FileDatasource) datasource ).getFile(); file = getAbsoluteFilePath( file ); datasource.setNativeCoordinateSystem( CRSFactory.create( WGS84 ) ); FileSystemAccessFactory fsaf = FileSystemAccessFactory.getInstance( mapModel.getApplicationContainer() ); InputStream is = null; try { FileSystemAccess fsa = fsaf.getFileSystemAccess( FILECHOOSERTYPE.geoDataFile ); is = fsa.getFileURL( file.getAbsolutePath() ).openStream(); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10070", file.getAbsolutePath(), e.getMessage() ) ); } FeatureCollection featureCollection = GPXReader.read( is ); featureCollection.setEnvelopesUpdated(); featureCollection = transformToMapModelCrs( featureCollection ); try { if ( featureCollection.size() > 0 ) { datasource.setExtent( featureCollection.getBoundedBy() ); } else { datasource.setExtent( mapModel.getEnvelope() ); } } catch ( GeometryException e ) { LOG.logError( "Unknown error", e ); } featureCollections.put( datasource.getName(), featureCollection ); } private void loadGMLFile() { GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument( true ); File file = ( (FileDatasource) datasource ).getFile(); file = getAbsoluteFilePath( file ); FileSystemAccessFactory fsaf = FileSystemAccessFactory.getInstance( mapModel.getApplicationContainer() ); try { FileSystemAccess fsa = fsaf.getFileSystemAccess( FILECHOOSERTYPE.geoDataFile ); URL url = fsa.getFileURL( file.getAbsolutePath() ); doc.load( url ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10070", file.getAbsolutePath(), e.getMessage() ) ); } FeatureCollection featureCollection; try { featureCollection = doc.parse(); if ( featureCollection.size() > 0 ) { datasource.setExtent( featureCollection.getBoundedBy() ); } else { datasource.setExtent( mapModel.getEnvelope() ); } } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10071", file.getAbsolutePath(), e.getMessage() ) ); } featureCollection = transformToMapModelCrs( featureCollection ); featureCollections.put( datasource.getName(), featureCollection ); } private File getAbsoluteFilePath( File file ) { if ( !file.isAbsolute() ) { try { URL url = mapModel.getApplicationContainer().resolve( file.getPath() ); file = new File( url.toExternalForm().substring( 5 ) ); } catch ( MalformedURLException e ) { // should never happen LOG.logError( e ); } } return file; } /** * reads data from a shape file restricted by the current bounding box of the map model into the internal feature * collection. This method will be called if file type is shape file and data source is lazy loading. */ private void loadShapeFile() { fireStartLoadingEvent(); Envelope currentEnv = null; if ( this.isLazyLoading ) { currentEnv = mapModel.getEnvelope(); } else { currentEnv = mapModel.getMaxExtent(); } // transform current envelope into native CRS of the data source if required if ( !mapModel.getCoordinateSystem().equals( datasource.getNativeCoordinateSystem() ) ) { GeoTransformer gt = new GeoTransformer( datasource.getNativeCoordinateSystem() ); try { currentEnv = gt.transform( currentEnv, mapModel.getCoordinateSystem().getPrefixedName(), true ); } catch ( Exception e ) { LOG.logError( e ); throw new DataAccessException( e.getMessage() ); } } File file = ( (FileDatasource) datasource ).getFile(); file = getAbsoluteFilePath( file ); int p = file.getAbsolutePath().lastIndexOf( '.' ); String fileName = file.getAbsolutePath().substring( 0, p ); ShapeFile sf = null; try { sf = new ShapeFile( fileName ); } catch ( Exception e ) { fireLoadingExceptionEvent(); LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10009", file.getAbsolutePath(), e.getMessage() ) ); } int[] ids; try { ids = sf.getGeoNumbersByRect( currentEnv ); } catch ( IOException e ) { fireLoadingExceptionEvent(); LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10011", file.getAbsolutePath(), e.getMessage() ) ); } if ( ids == null ) { ids = new int[0]; } String id = "UUID_" + UUID.randomUUID().toString(); FeatureCollection featureCollection = FeatureFactory.createFeatureCollection( id, ids.length ); for ( int i = 0; i < ids.length; i++ ) { Feature feat; try { feat = sf.getFeatureByRecNo( ids[i] ); } catch ( Exception e ) { fireLoadingExceptionEvent(); LOG.logError( e.getMessage(), e ); throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10010", file.getAbsolutePath(), e.getMessage() ) ); } GeometryImpl gm = (GeometryImpl) feat.getDefaultGeometryPropertyValue(); gm.setCoordinateSystem( datasource.getNativeCoordinateSystem() ); featureCollection.add( feat ); } sf.close(); featureCollection = transformToMapModelCrs( featureCollection ); featureCollections.put( datasource.getName(), featureCollection ); fireLoadingFinishedEvent(); } @Override public synchronized FeatureCollection getFeatureCollection() { if ( this.isLazyLoading ) { synchronized ( datasource ) { double min = datasource.getMinScaleDenominator(); double max = datasource.getMaxScaleDenominator(); if ( mapModel.getScaleDenominator() >= min && mapModel.getScaleDenominator() < max ) { Envelope currentEnv = mapModel.getEnvelope(); if ( lastEnv == null || ( !currentEnv.equals( lastEnv ) && !lastEnv.contains( currentEnv ) ) ) { String nm = ( (FileDatasource) datasource ).getFile().getName().toLowerCase(); if ( nm.endsWith( ".shp" ) ) { loadShapeFile(); } else { DialogFactory.openErrorDialog( layer.getOwner().getApplicationContainer().getViewPlatform(), null, "lazy loading not supported for file: " + nm, "error lazy loading" ); } lastEnv = mapModel.getEnvelope(); } // perform all inserts, updates, deletes that has been performed on this // data source adapter on the feature collection read from the adapted datasource updateFeatureCollection(); } } } else if ( featureCollections.get( datasource.getName() ) == null ) { loadData(); } return featureCollections.get( datasource.getName() ); } /* * (non-Javadoc) * * @see org.deegree.client.presenter.connector.IMapModelAdapter#refresh() */ public void refresh() { if ( featureCollections.get( datasource.getName() ) == null && !this.isLazyLoading ) { // if feature collection has already been loaded for a not lazy loading datasource // it don't have to loaded again. refreshData(); } loadSchema(); } private void refreshData() { loadData(); layer.setDataRefreshed( this ); } @Override public void refresh( boolean forceReload ) { if ( forceReload ) { refreshData(); } else { refresh(); } } @Override public void commitChanges() throws IOException { LOG.logDebug( "commiting changes to: " + datasource.getName() ); if ( this.isLazyLoading ) { LOG.logInfo( "commit changes on a lazy loading file based layer is not supported yet" ); } else { File file = ( (FileDatasource) datasource ).getFile(); FeatureCollection fc = getFeatureCollection(); try { file.delete(); String outName = getAbsoluteFilePath( file ).getAbsolutePath(); if ( outName.toLowerCase().endsWith( ".gml" ) || outName.toLowerCase().endsWith( ".xml" ) ) { writeAsGml( fc, outName ); } else { writeAsShape( fc, outName ); } } catch ( Exception e ) { LOG.logError( "commiting data to datasource: " + datasource.getName() + " failed; restoring backend from backup", e ); // FileUtils.copy( backup, file ); } } } private void writeAsShape( FeatureCollection featureCollection, String outName ) throws DBaseException, GeometryException, IOException { featureCollection = cloneFeatureCollection( featureCollection ); featureCollection = transformToDatasourceCrs( featureCollection ); outName = outName.substring( 0, outName.lastIndexOf( '.' ) ); org.deegree.io.shpapi.shape_new.ShapeFile sf = new org.deegree.io.shpapi.shape_new.ShapeFile( featureCollection, outName ); ShapeFileWriter writer = new ShapeFileWriter( sf ); writer.write(); } private FeatureCollection cloneFeatureCollection( FeatureCollection featureCollection ) { try { featureCollection = (FeatureCollection) featureCollection.cloneDeep(); } catch ( Exception e ) { LOG.logInfo( "Could not clone feature collection to store." ); } return featureCollection; } private void writeAsGml( FeatureCollection fc, String outName ) throws FileNotFoundException, IOException, FeatureException, SAXException { FileOutputStream fos = new FileOutputStream( new File( outName ) ); GMLFeatureAdapter ada = new GMLFeatureAdapter(); GMLFeatureCollectionDocument doc = ada.export( fc ); doc.write( fos ); fos.close(); } }