/** * '$RCSfile$' * Copyright: 2003 Regents of the University of California. * * Author: Matthew Perry * '$Date: 2011-12-07 20:18:24 +0000 (Wed, 07 Dec 2011) $' * '$Revision: 6744 $' * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.ucsb.nceas.metacat.spatial; import edu.ucsb.nceas.metacat.database.DBConnection; import edu.ucsb.nceas.metacat.properties.PropertyService; import edu.ucsb.nceas.metacat.util.MetacatUtil; import edu.ucsb.nceas.metacat.util.SystemUtil; import edu.ucsb.nceas.utilities.PropertyNotFoundException; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.PrecisionModel; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import java.sql.ResultSet; import java.sql.PreparedStatement; import java.util.Vector; import org.apache.log4j.Logger; /** * * Class representing the spatial portions of an xml document as a geotools * Feature. */ public class SpatialDocument { private DBConnection dbconn; private static Logger log = Logger.getLogger(SpatialDocument.class.getName()); private SpatialFeatureSchema featureSchema = new SpatialFeatureSchema(); Vector west = new Vector(); Vector south = new Vector(); Vector east = new Vector(); Vector north = new Vector(); String title = ""; String docid = null; /** * Constructor that queries the db * * @param docid * The document id to be represented spatially * @param dbconn * The database connection shared from the refering method. */ public SpatialDocument(String docid, DBConnection dbconn) { this.docid = docid; PreparedStatement pstmt = null; ResultSet rs = null; this.dbconn = dbconn; boolean isSpatialDocument = false; String thisDocname = null; String westPath = null; String eastPath = null; String northPath = null; String southPath = null; /* * Determine the docname/schema and decide how to proceed with spatial * harvest */ String query = "SELECT docname FROM xml_documents WHERE docid = ?"; String docname = ""; try { pstmt = dbconn.prepareStatement(query); pstmt.setString(1, docid.trim()); pstmt.execute(); rs = pstmt.getResultSet(); while (rs.next()) { docname = rs.getString(1); } rs.close(); pstmt.close(); } catch (Exception e) { log.error(" ---- Could not get docname for " + docid); e.printStackTrace(); } if (docname == null) docname = ""; // Loop through all our spatial docnames and determine if the current // document matches // If so, get the appropriate corner xpaths try { Vector spatialDocnames = MetacatUtil.getOptionList(PropertyService .getProperty("spatial.spatialDocnameList")); for (int i = 0; i < spatialDocnames.size(); i++) { thisDocname = ((String) spatialDocnames.elementAt(i)).trim(); if (docname.trim().equals(thisDocname)) { isSpatialDocument = true; // determine its east,west,north and south coord xpaths westPath = PropertyService.getProperty("spatial." + thisDocname + "_westBoundingCoordinatePath"); eastPath = PropertyService.getProperty("spatial." + thisDocname + "_eastBoundingCoordinatePath"); northPath = PropertyService.getProperty("spatial." + thisDocname + "_northBoundingCoordinatePath"); southPath = PropertyService.getProperty("spatial." + thisDocname + "_southBoundingCoordinatePath"); } } } catch (PropertyNotFoundException pnfe) { log.error("Could not find spatialDocnameList or bounding coordinate " + "path for: " + docid); pnfe.printStackTrace(); } // If it is a spatial document, harvest the corners and title if (isSpatialDocument) { /* * Get the bounding coordinates for current public revision */ query = "SELECT path, nodedatanumerical, parentnodeid FROM xml_path_index" + " WHERE docid = ?" + " AND docid IN " + "(SELECT distinct id.docid " + "FROM identifier id, xml_access xa, xml_documents xd " + " WHERE id.docid = ?" + " AND id.docid = xd.docid " + " AND id.rev = xd.rev " + " AND id.guid = xa.guid " + " AND xa.principal_name = 'public' AND xa.perm_type = 'allow')" + " AND (path = '" + westPath + "'" + " OR path = '" + southPath + "'" + " OR path = '" + eastPath + "'" + " OR path = '" + northPath + "'" + " ) ORDER BY parentnodeid;"; try { pstmt = dbconn.prepareStatement(query); pstmt.setString(1, docid.trim()); pstmt.setString(2, docid.trim()); pstmt.execute(); rs = pstmt.getResultSet(); while (rs.next()) { if (rs.getString(1).equals(westPath)) this.west.add(new Float(rs.getFloat(2))); else if (rs.getString(1).equals(southPath)) this.south.add(new Float(rs.getFloat(2))); else if (rs.getString(1).equals(eastPath)) this.east.add(new Float(rs.getFloat(2))); else if (rs.getString(1).equals(northPath)) this.north.add(new Float(rs.getFloat(2))); else log.error("** An xml path not related to your bounding coordinates was returned by this query \n" + query + "\n"); } rs.close(); pstmt.close(); } catch (Exception e) { log.error(" ---- Could not get bounding coordinates for " + docid); e.printStackTrace(); } /* * Get the title */ try { String docTitlePath = PropertyService.getProperty("spatial.docTitle"); query = "select nodedata from xml_path_index where path = ?" + " and docid = ?"; pstmt = dbconn.prepareStatement(query); pstmt.setString(1, docTitlePath.trim()); pstmt.setString(2, docid.trim()); pstmt.execute(); rs = pstmt.getResultSet(); if (rs.next()) this.title = rs.getString(1); rs.close(); pstmt.close(); } catch (Exception e) { log.error(" **** Error getting docids from getTitle for docid = " + docid); e.printStackTrace(); this.title = docid; } } } /** * Returns a geotools (multi)polygon feature with geometry plus attributes * ready to be inserted into our spatial dataset cache */ public SimpleFeature getPolygonFeature() { // Get polygon feature type SimpleFeatureType polyType = featureSchema.getPolygonFeatureType(); MultiPolygon theGeom = getPolygonGeometry(); if (theGeom == null) return null; // Populate the feature schema try { SimpleFeatureBuilder builder = new SimpleFeatureBuilder(polyType); builder.addAll( new Object[]{ theGeom, this.docid, getUrl(this.docid), this.title } ); SimpleFeature polyFeature = builder.buildFeature(this.docid); return polyFeature; } catch (Exception e) { log.error("Problem getting polygon feature: " + e.getMessage()); return null; } } /** * Returns a geotools (multi)point feature with geometry plus attributes * ready to be inserted into our spatial dataset cache * */ public SimpleFeature getPointFeature() { // Get polygon feature type SimpleFeatureType pointType = featureSchema.getPointFeatureType(); MultiPoint theGeom = getPointGeometry(); if (theGeom == null) return null; // Populate the feature schema try { SimpleFeatureBuilder builder = new SimpleFeatureBuilder(pointType); builder.addAll( new Object[]{ theGeom, this.docid, getUrl(this.docid), this.title } ); SimpleFeature pointFeature = builder.buildFeature(this.docid); return pointFeature; } catch (Exception e) { log.error("Problem getting point feature: " + e.getMessage()); return null; } } /** * Given a valid docid, return an appropriate URL * for viewing the metadata document * * @param docid The document id for which to construct the access url. */ private String getUrl( String docid ) { String docUrl = null; try { docUrl = SystemUtil.getServletURL() + "?action=read&docid=" + docid + "&qformat=" + PropertyService.getProperty("application.default-style"); } catch (PropertyNotFoundException pnfe) { log.error("Could not get access url because of unavailable property: " + pnfe.getMessage()); } return docUrl; } /** * Returns a mutlipolygon geometry representing the geographic coverage(s) of the document * */ private MultiPolygon getPolygonGeometry() { PrecisionModel precModel = new PrecisionModel(); // default: Floating point GeometryFactory geomFac = new GeometryFactory( precModel, featureSchema.srid ); Vector polygons = new Vector(); float w; float s; float e; float n; if ( west.size() == south.size() && south.size() == east.size() && east.size() == north.size() ) { for (int i = 0; i < west.size(); i++) { w = ((Float)west.elementAt(i)).floatValue(); s = ((Float)south.elementAt(i)).floatValue(); e = ((Float)east.elementAt(i)).floatValue(); n = ((Float)north.elementAt(i)).floatValue(); // Check if it's actually a valid polygon if ( w == 0.0 && s == 0.0 && e == 0.0 && n == 0.0) { log.warn(" Invalid or empty coodinates ... skipping"); continue; } else if( Float.compare(w, e) == 0 && Float.compare(n,s) == 0 ) { log.warn(" Point coordinates only.. skipping polygon generation"); continue; } // Handle the case of crossing the dateline and poles // dateline crossing is valid // polar crossing is not ( so we swap north and south ) // Assumes all coordinates are confined to -180 -90 180 90 float dl = 180.0f; float _dl = -180.0f; if ( w > e && s > n ) { log.info( "Crosses both the dateline and the poles .. split into 2 polygons, swap n & s" ); polygons.add( createPolygonFromBbox( geomFac, w, n, dl, s ) ); polygons.add( createPolygonFromBbox( geomFac, _dl, n, e, s ) ); } else if ( w > e ) { log.info( "Crosses the dateline .. split into 2 polygons" ); polygons.add( createPolygonFromBbox( geomFac, w, s, dl, n ) ); polygons.add( createPolygonFromBbox( geomFac, _dl, s, e, n ) ); } else if ( s > n ) { log.info( "Crosses the poles .. swap north and south" ); polygons.add( createPolygonFromBbox( geomFac, w, n, e, s ) ); } else { // Just a standard polygon that fits nicely onto our flat earth polygons.add( createPolygonFromBbox( geomFac, w, s, e, n ) ); } } } else { log.error(" *** Something went wrong.. your east,west,north and south bounding arrays are different sizes!"); } if( polygons.size() > 0 ) { Polygon[] polyArray = geomFac.toPolygonArray( polygons ); MultiPolygon multiPolyGeom= geomFac.createMultiPolygon( polyArray ); return multiPolyGeom; } else { return null; } } /** * Returns a polygon given the four bounding box coordinates */ private Polygon createPolygonFromBbox( GeometryFactory geomFac, float w, float s, float e, float n ) { Coordinate[] linestringCoordinates = new Coordinate[5]; linestringCoordinates[0] = new Coordinate( w, s ); linestringCoordinates[1] = new Coordinate( w, n ); linestringCoordinates[2] = new Coordinate( e, n ); linestringCoordinates[3] = new Coordinate( e, s ); linestringCoordinates[4] = new Coordinate( w, s ); return geomFac.createPolygon( geomFac.createLinearRing(linestringCoordinates), null); } /** * Returns a multipoint geometry represnting the geographic coverage(s) of the document * * @todo Handle the case of crossing the dateline and poles */ private MultiPoint getPointGeometry() { PrecisionModel precModel = new PrecisionModel(); // default: Floating point GeometryFactory geomFac = new GeometryFactory( precModel, featureSchema.srid ); float w; float s; float e; float n; PreparedStatement pstmt = null; ResultSet rs = null; Vector points = new Vector(); if ( west.size() == south.size() && south.size() == east.size() && east.size() == north.size() ) { for (int i = 0; i < west.size(); i++) { w = ((Float)west.elementAt(i)).floatValue(); s = ((Float)south.elementAt(i)).floatValue(); e = ((Float)east.elementAt(i)).floatValue(); n = ((Float)north.elementAt(i)).floatValue(); // Check if it's actually a valid point if ( w == 0.0f && s == 0.0f && e == 0.0f && n == 0.0f) { log.warn(" Invalid or empty coodinates ... skipping"); continue; } float xCenter; float yCenter; // Handle the case of crossing the dateline and poles // Assumes all coordinates are confined to -180 -90 180 90 if ( w > e ) { log.info( "Crosses the dateline .. " ); xCenter = (360.0f - w + e)/ 2.0f + w; if( xCenter > 180.0f ) xCenter = xCenter - 360.0f; yCenter = ( s + n ) / 2.0f; } else { // Just a standard point that can be calculated by the average coordinates xCenter = ( w + e ) / 2.0f; yCenter = ( s + n ) / 2.0f; } points.add( geomFac.createPoint( new Coordinate( xCenter, yCenter)) ); } } else { log.error(" *** Something went wrong.. your east,west,north and south bounding vectors are different sizes!"); } if( points.size() > 0 ) { Point[] pointArray = geomFac.toPointArray( points ); MultiPoint multiPointGeom= geomFac.createMultiPoint( pointArray ); return multiPointGeom; } else { return null; } } }