Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2003 Regents of the University of California.
4
 *
5
 * Author: Matthew Perry 
6
 * '$Date: 2011-11-02 20:40:12 -0700 (Wed, 02 Nov 2011) $'
7
 * '$Revision: 6595 $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23
package edu.ucsb.nceas.metacat.spatial;
24

    
25
import edu.ucsb.nceas.metacat.database.DBConnection;
26
import edu.ucsb.nceas.metacat.properties.PropertyService;
27
import edu.ucsb.nceas.metacat.util.MetacatUtil;
28
import edu.ucsb.nceas.metacat.util.SystemUtil;
29
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
30

    
31
import com.vividsolutions.jts.geom.Coordinate;
32
import com.vividsolutions.jts.geom.Point;
33
import com.vividsolutions.jts.geom.Polygon;
34
import com.vividsolutions.jts.geom.MultiPolygon;
35
import com.vividsolutions.jts.geom.MultiPoint;
36
import com.vividsolutions.jts.geom.GeometryFactory;
37
import com.vividsolutions.jts.geom.PrecisionModel;
38

    
39
import org.geotools.feature.simple.SimpleFeatureBuilder;
40
import org.opengis.feature.simple.SimpleFeature;
41
import org.opengis.feature.simple.SimpleFeatureType;
42

    
43
import java.sql.ResultSet;
44
import java.sql.PreparedStatement;
45
import java.util.Vector;
46

    
47
import org.apache.log4j.Logger;
48

    
49
/**
50
 * 
51
 * Class representing the spatial portions of an xml document as a geotools
52
 * Feature.
53
 */
54
public class SpatialDocument {
55

    
56
	private DBConnection dbconn;
57

    
58
	private static Logger log = Logger.getLogger(SpatialDocument.class.getName());
59

    
60
	private SpatialFeatureSchema featureSchema = new SpatialFeatureSchema();
61

    
62
	Vector west = new Vector();
63
	Vector south = new Vector();
64
	Vector east = new Vector();
65
	Vector north = new Vector();
66

    
67
	String title = "";
68
	String docid = null;
69

    
70
	/**
71
	 * Constructor that queries the db
72
	 * 
73
	 * @param docid
74
	 *            The document id to be represented spatially
75
	 * @param dbconn
76
	 *            The database connection shared from the refering method.
77
	 */
78
	public SpatialDocument(String docid, DBConnection dbconn) {
79

    
80
		this.docid = docid;
81
		PreparedStatement pstmt = null;
82
		ResultSet rs = null;
83
		this.dbconn = dbconn;
84
		boolean isSpatialDocument = false;
85
		String thisDocname = null;
86
		String westPath = null;
87
		String eastPath = null;
88
		String northPath = null;
89
		String southPath = null;
90

    
91
		/*
92
		 * Determine the docname/schema and decide how to proceed with spatial
93
		 * harvest
94
		 */
95
		String query = "SELECT docname FROM xml_documents WHERE docid = ?";
96
		String docname = "";
97
		try {
98
			pstmt = dbconn.prepareStatement(query);
99
			pstmt.setString(1, docid.trim());
100
			pstmt.execute();
101
			rs = pstmt.getResultSet();
102
			while (rs.next()) {
103
				docname = rs.getString(1);
104
			}
105
			rs.close();
106
			pstmt.close();
107
		} catch (Exception e) {
108
			log.error(" ---- Could not get docname for " + docid);
109
			e.printStackTrace();
110
		}
111
		if (docname == null)
112
			docname = "";
113

    
114
		// Loop through all our spatial docnames and determine if the current
115
		// document matches
116
		// If so, get the appropriate corner xpaths
117
		try {
118
			Vector spatialDocnames = MetacatUtil.getOptionList(PropertyService
119
					.getProperty("spatial.spatialDocnameList"));
120
			for (int i = 0; i < spatialDocnames.size(); i++) {
121
				thisDocname = ((String) spatialDocnames.elementAt(i)).trim();
122
				if (docname.trim().equals(thisDocname)) {
123
					isSpatialDocument = true;
124

    
125
					// determine its east,west,north and south coord xpaths
126
					westPath = PropertyService.getProperty("spatial." + thisDocname
127
							+ "_westBoundingCoordinatePath");
128
					eastPath = PropertyService.getProperty("spatial." + thisDocname
129
							+ "_eastBoundingCoordinatePath");
130
					northPath = PropertyService.getProperty("spatial." + thisDocname
131
							+ "_northBoundingCoordinatePath");
132
					southPath = PropertyService.getProperty("spatial." + thisDocname
133
							+ "_southBoundingCoordinatePath");
134
				}
135
			}
136
		} catch (PropertyNotFoundException pnfe) {
137
			log.error("Could not find spatialDocnameList or bounding coordinate "
138
					+ "path for: " + docid);
139
			pnfe.printStackTrace();
140
		}
141

    
142
		// If it is a spatial document, harvest the corners and title
143
		if (isSpatialDocument) {
144

    
145
			/*
146
			 * Get the bounding coordinates
147
			 */
148
			query = "SELECT path, nodedatanumerical, parentnodeid FROM xml_path_index"
149
					+ " WHERE docid = ?"
150
					+ " AND docid IN (SELECT distinct docid FROM xml_access WHERE docid = ?"
151
					+ " AND principal_name = 'public' AND perm_type = 'allow')"
152
					+ " AND (path = '" + westPath + "'" + "  OR path = '" + southPath
153
					+ "'" + "  OR path = '" + eastPath + "'" + "  OR path = '"
154
					+ northPath + "'" + " ) ORDER BY parentnodeid;";
155

    
156
			try {
157
				pstmt = dbconn.prepareStatement(query);
158
				pstmt.setString(1, docid.trim());
159
				pstmt.setString(2, docid.trim());
160
				pstmt.execute();
161
				rs = pstmt.getResultSet();
162
				while (rs.next()) {
163
					if (rs.getString(1).equals(westPath))
164
						this.west.add(new Float(rs.getFloat(2)));
165
					else if (rs.getString(1).equals(southPath))
166
						this.south.add(new Float(rs.getFloat(2)));
167
					else if (rs.getString(1).equals(eastPath))
168
						this.east.add(new Float(rs.getFloat(2)));
169
					else if (rs.getString(1).equals(northPath))
170
						this.north.add(new Float(rs.getFloat(2)));
171
					else
172
						log.error("** An xml path not related to your bounding coordinates was returned by this query \n"
173
										+ query + "\n");
174
				}
175
				rs.close();
176
				pstmt.close();
177
			} catch (Exception e) {
178
				log.error(" ---- Could not get bounding coordinates for " + docid);
179
				e.printStackTrace();
180
			}
181

    
182
			/*
183
			 * Get the title
184
			 */
185

    
186
			try {
187

    
188
				String docTitlePath = PropertyService.getProperty("spatial.docTitle");
189
				query = "select nodedata from xml_path_index where path = ?"
190
						+ " and docid = ?";
191
				pstmt = dbconn.prepareStatement(query);
192
				pstmt.setString(1, docTitlePath.trim());
193
				pstmt.setString(2, docid.trim());
194
				pstmt.execute();
195
				rs = pstmt.getResultSet();
196
				if (rs.next())
197
					this.title = rs.getString(1);
198
				rs.close();
199
				pstmt.close();
200
			} catch (Exception e) {
201
				log.error(" **** Error getting docids from getTitle for docid = "
202
								+ docid);
203
				e.printStackTrace();
204
				this.title = docid;
205
			}
206
		}
207

    
208
  }
209

    
210
  /**
211
	 * Returns a geotools (multi)polygon feature with geometry plus attributes
212
	 * ready to be inserted into our spatial dataset cache
213
	 */
214
  public SimpleFeature getPolygonFeature() {
215
      // Get polygon feature type
216
      SimpleFeatureType polyType = featureSchema.getPolygonFeatureType();
217

    
218
      MultiPolygon theGeom = getPolygonGeometry();
219
      if (theGeom == null)
220
          return null;
221

    
222
      // Populate the feature schema
223
      try {
224
    	  SimpleFeatureBuilder builder = new SimpleFeatureBuilder(polyType);
225
    	  builder.addAll(
226
    			  new Object[]{ 
227
	                  theGeom,
228
	                  this.docid,
229
	                  getUrl(this.docid), 
230
	                  this.title }
231
    			  );
232
          SimpleFeature polyFeature = builder.buildFeature(this.docid);
233
          return polyFeature; 
234
      } catch (Exception e) {
235
          log.error("Problem getting polygon feature: " + e.getMessage());
236
          return null;
237
      }
238
  }
239

    
240
  /**
241
   * Returns a geotools (multi)point feature with geometry plus attributes
242
   * ready to be inserted into our spatial dataset cache
243
   *
244
   */
245
  public SimpleFeature getPointFeature() {
246
      // Get polygon feature type
247
      SimpleFeatureType pointType = featureSchema.getPointFeatureType();
248

    
249
      MultiPoint theGeom = getPointGeometry();
250
      if (theGeom == null)
251
          return null;
252

    
253
      // Populate the feature schema
254
      try {
255
    	  SimpleFeatureBuilder builder = new SimpleFeatureBuilder(pointType);
256
    	  builder.addAll(
257
    			  new Object[]{ 
258
	                  theGeom,
259
	                  this.docid,
260
	                  getUrl(this.docid), 
261
	                  this.title }
262
    			  );
263
          SimpleFeature pointFeature = builder.buildFeature(this.docid);
264
          return pointFeature;
265
      } catch (Exception e) {
266
          log.error("Problem getting point feature: " + e.getMessage());
267
          return null;
268
      }
269
  }
270

    
271
  /**
272
   * Given a valid docid, return an appropriate URL
273
   * for viewing the metadata document
274
   *
275
   * @param docid The document id for which to construct the access url.
276
   */
277
  private String getUrl( String docid ) {
278
     String docUrl = null;
279
     try {
280
    	 docUrl = SystemUtil.getServletURL()
281
                    + "?action=read&docid=" + docid 
282
                    + "&qformat=" 
283
                    + PropertyService.getProperty("application.default-style");
284
     } catch (PropertyNotFoundException pnfe) {
285
    	 log.error("Could not get access url because of unavailable property: " 
286
    			 + pnfe.getMessage());
287
     }
288

    
289
     return docUrl;
290
  }
291

    
292

    
293
  /**
294
   * Returns a mutlipolygon geometry representing the geographic coverage(s) of the document
295
   *
296
   */
297
  private MultiPolygon getPolygonGeometry() {
298

    
299
    PrecisionModel precModel = new PrecisionModel(); // default: Floating point
300
    GeometryFactory geomFac = new GeometryFactory( precModel, featureSchema.srid );
301
    Vector polygons = new Vector();
302
    float w;
303
    float s;
304
    float e;
305
    float n;
306

    
307
    if ( west.size() == south.size() && south.size() == east.size() && east.size() == north.size() ) {
308
        for (int i = 0; i < west.size(); i++) {
309

    
310
            w = ((Float)west.elementAt(i)).floatValue();
311
            s = ((Float)south.elementAt(i)).floatValue();
312
            e = ((Float)east.elementAt(i)).floatValue();
313
            n = ((Float)north.elementAt(i)).floatValue();
314

    
315
            // Check if it's actually a valid polygon
316
            if (  w == 0.0 && s == 0.0 && e == 0.0 && n == 0.0) {
317
                log.warn("        Invalid or empty coodinates ... skipping");
318
                continue;
319
            } else if( Float.compare(w, e) == 0 && Float.compare(n,s) == 0 ) {
320
                log.warn("        Point coordinates only.. skipping polygon generation");
321
                continue;
322
            }
323

    
324
            // Handle the case of crossing the dateline and poles
325
            // dateline crossing is valid 
326
            // polar crossing is not ( so we swap north and south )
327
            // Assumes all coordinates are confined to -180 -90 180 90
328
            float dl = 180.0f;
329
            float _dl = -180.0f;
330
            
331
            if ( w > e && s > n ) {
332
                log.info( "Crosses both the dateline and the poles .. split into 2 polygons, swap n & s" );
333
                polygons.add( createPolygonFromBbox( geomFac,   w,   n, dl, s ) );
334
                polygons.add( createPolygonFromBbox( geomFac, _dl,   n,  e, s ) );
335
            } else if ( w > e ) {
336
                log.info( "Crosses the dateline .. split into 2 polygons" );
337
                polygons.add( createPolygonFromBbox( geomFac,   w, s, dl, n ) );
338
                polygons.add( createPolygonFromBbox( geomFac, _dl, s,  e, n ) );
339
            } else if ( s > n ) {
340
                log.info( "Crosses the poles .. swap north and south" );
341
                polygons.add( createPolygonFromBbox( geomFac, w, n, e, s ) );
342
            } else {
343
                // Just a standard polygon that fits nicely onto our flat earth
344
                polygons.add( createPolygonFromBbox( geomFac, w, s, e, n ) );    
345
            }
346

    
347
             
348
        }
349
    } else {
350
       log.error(" *** Something went wrong.. your east,west,north and south bounding arrays are different sizes!");
351
    }
352
    
353
    if( polygons.size() > 0 ) {
354
       Polygon[] polyArray = geomFac.toPolygonArray( polygons );
355
       MultiPolygon multiPolyGeom= geomFac.createMultiPolygon( polyArray );
356
       return multiPolyGeom; 
357
    } else {
358
       return null;
359
    } 
360

    
361
  }
362
   
363

    
364
  /**
365
   * Returns a polygon given the four bounding box coordinates
366
   */
367
  private Polygon createPolygonFromBbox( GeometryFactory geomFac, float w, float s, float e, float n ) {
368

    
369
        Coordinate[] linestringCoordinates = new Coordinate[5];
370

    
371
        linestringCoordinates[0] = new Coordinate( w, s );
372
        linestringCoordinates[1] = new Coordinate( w, n );
373
        linestringCoordinates[2] = new Coordinate( e, n );
374
        linestringCoordinates[3] = new Coordinate( e, s );
375
        linestringCoordinates[4] = new Coordinate( w, s );
376

    
377
        return geomFac.createPolygon( geomFac.createLinearRing(linestringCoordinates), null);
378
  }
379

    
380

    
381
  /**
382
   * Returns a multipoint geometry represnting the geographic coverage(s) of the document
383
   *
384
   * @todo Handle the case of crossing the dateline and poles
385
   */
386
  private MultiPoint getPointGeometry() {
387

    
388
    PrecisionModel precModel = new PrecisionModel(); // default: Floating point
389
    GeometryFactory geomFac = new GeometryFactory( precModel, featureSchema.srid );
390
    float w;
391
    float s;
392
    float e;
393
    float n;
394

    
395
    PreparedStatement pstmt = null;
396
    ResultSet rs = null;
397

    
398
    Vector points = new Vector();
399

    
400
    if ( west.size() == south.size() && south.size() == east.size() && east.size() == north.size() ) {
401
        for (int i = 0; i < west.size(); i++) {
402

    
403
            w = ((Float)west.elementAt(i)).floatValue();
404
            s = ((Float)south.elementAt(i)).floatValue();
405
            e = ((Float)east.elementAt(i)).floatValue();
406
            n = ((Float)north.elementAt(i)).floatValue();
407

    
408
            // Check if it's actually a valid point
409
            if (  w == 0.0f && s == 0.0f && e == 0.0f && n == 0.0f) {
410
                 log.warn("        Invalid or empty coodinates ... skipping");
411
                 continue;
412
            }
413

    
414
            float xCenter;
415
            float yCenter;
416

    
417
            // Handle the case of crossing the dateline and poles
418
            // Assumes all coordinates are confined to -180 -90 180 90
419

    
420
            if ( w > e ) {
421
                log.info( "Crosses the dateline .. " );
422
                xCenter = (360.0f - w + e)/ 2.0f + w;
423
                if( xCenter > 180.0f )
424
                    xCenter = xCenter - 360.0f;
425
                yCenter = ( s + n ) / 2.0f;
426
            } else {
427
                // Just a standard point that can be calculated by the average coordinates
428
                xCenter = ( w + e ) / 2.0f;
429
                yCenter = ( s + n ) / 2.0f;
430
            }
431

    
432
            points.add( geomFac.createPoint( new Coordinate( xCenter, yCenter)) );
433
        }
434
    } else {
435
       log.error(" *** Something went wrong.. your east,west,north and south bounding vectors are different sizes!");
436
    }
437
    
438
    if( points.size() > 0 ) {
439
       Point[] pointArray = geomFac.toPointArray( points );
440
       MultiPoint multiPointGeom= geomFac.createMultiPoint( pointArray );
441
       return multiPointGeom; 
442
    } else {
443
       return null;
444
    } 
445

    
446

    
447
  }
448
}
(3-3/8)