Project

General

Profile

1 3035 perry
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2003 Regents of the University of California.
4
 *
5
 * Author: Matthew Perry
6
 * '$Date$'
7
 * '$Revision$'
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 5015 daigle
import edu.ucsb.nceas.metacat.database.DBConnection;
26 5030 daigle
import edu.ucsb.nceas.metacat.properties.PropertyService;
27 4699 daigle
import edu.ucsb.nceas.metacat.util.MetacatUtil;
28 4080 daigle
import edu.ucsb.nceas.metacat.util.SystemUtil;
29
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
30 3035 perry
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 5829 leinfelder
import org.geotools.feature.simple.SimpleFeatureBuilder;
40
import org.opengis.feature.simple.SimpleFeature;
41
import org.opengis.feature.simple.SimpleFeatureType;
42 3035 perry
43
import java.sql.ResultSet;
44
import java.sql.PreparedStatement;
45
import java.util.Vector;
46
47
import org.apache.log4j.Logger;
48
49 3040 perry
/**
50 4080 daigle
 *
51
 * Class representing the spatial portions of an xml document as a geotools
52
 * Feature.
53 3040 perry
 */
54 3035 perry
public class SpatialDocument {
55
56 4080 daigle
	private DBConnection dbconn;
57 3035 perry
58 4080 daigle
	private static Logger log = Logger.getLogger(SpatialDocument.class.getName());
59 3035 perry
60 4080 daigle
	private SpatialFeatureSchema featureSchema = new SpatialFeatureSchema();
61 3035 perry
62 4080 daigle
	Vector west = new Vector();
63
	Vector south = new Vector();
64
	Vector east = new Vector();
65
	Vector north = new Vector();
66 3035 perry
67 4080 daigle
	String title = "";
68
	String docid = null;
69 3035 perry
70 4080 daigle
	/**
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 3035 perry
80 4080 daigle
		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 3035 perry
91 4080 daigle
		/*
92
		 * Determine the docname/schema and decide how to proceed with spatial
93
		 * harvest
94
		 */
95 6595 leinfelder
		String query = "SELECT docname FROM xml_documents WHERE docid = ?";
96 4080 daigle
		String docname = "";
97
		try {
98
			pstmt = dbconn.prepareStatement(query);
99 6595 leinfelder
			pstmt.setString(1, docid.trim());
100 4080 daigle
			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 3035 perry
114 4080 daigle
		// 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 4699 daigle
			Vector spatialDocnames = MetacatUtil.getOptionList(PropertyService
119 4178 daigle
					.getProperty("spatial.spatialDocnameList"));
120 4080 daigle
			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 3035 perry
125 4080 daigle
					// determine its east,west,north and south coord xpaths
126 4178 daigle
					westPath = PropertyService.getProperty("spatial." + thisDocname
127 4080 daigle
							+ "_westBoundingCoordinatePath");
128 4178 daigle
					eastPath = PropertyService.getProperty("spatial." + thisDocname
129 4080 daigle
							+ "_eastBoundingCoordinatePath");
130 4178 daigle
					northPath = PropertyService.getProperty("spatial." + thisDocname
131 4080 daigle
							+ "_northBoundingCoordinatePath");
132 4178 daigle
					southPath = PropertyService.getProperty("spatial." + thisDocname
133 4080 daigle
							+ "_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 3120 perry
142 4080 daigle
		// If it is a spatial document, harvest the corners and title
143
		if (isSpatialDocument) {
144 3120 perry
145 4080 daigle
			/*
146 6744 leinfelder
			 * Get the bounding coordinates for current public revision
147 4080 daigle
			 */
148
			query = "SELECT path, nodedatanumerical, parentnodeid FROM xml_path_index"
149 6595 leinfelder
					+ " WHERE docid = ?"
150 6744 leinfelder
					+ " AND docid IN " +
151
							"(SELECT distinct id.docid " +
152
							"FROM identifier id, xml_access xa, xml_documents xd "
153
							+ " WHERE id.docid = ?"
154
							+ " AND id.docid = xd.docid "
155
							+ " AND id.rev = xd.rev "
156
							+ " AND id.guid = xa.guid "
157
							+ " AND xa.principal_name = 'public' AND xa.perm_type = 'allow')"
158 4080 daigle
					+ " AND (path = '" + westPath + "'" + "  OR path = '" + southPath
159
					+ "'" + "  OR path = '" + eastPath + "'" + "  OR path = '"
160
					+ northPath + "'" + " ) ORDER BY parentnodeid;";
161 3120 perry
162 4080 daigle
			try {
163
				pstmt = dbconn.prepareStatement(query);
164 6595 leinfelder
				pstmt.setString(1, docid.trim());
165
				pstmt.setString(2, docid.trim());
166 4080 daigle
				pstmt.execute();
167
				rs = pstmt.getResultSet();
168
				while (rs.next()) {
169
					if (rs.getString(1).equals(westPath))
170
						this.west.add(new Float(rs.getFloat(2)));
171
					else if (rs.getString(1).equals(southPath))
172
						this.south.add(new Float(rs.getFloat(2)));
173
					else if (rs.getString(1).equals(eastPath))
174
						this.east.add(new Float(rs.getFloat(2)));
175
					else if (rs.getString(1).equals(northPath))
176
						this.north.add(new Float(rs.getFloat(2)));
177
					else
178
						log.error("** An xml path not related to your bounding coordinates was returned by this query \n"
179
										+ query + "\n");
180
				}
181
				rs.close();
182
				pstmt.close();
183
			} catch (Exception e) {
184
				log.error(" ---- Could not get bounding coordinates for " + docid);
185
				e.printStackTrace();
186
			}
187 3120 perry
188 4080 daigle
			/*
189
			 * Get the title
190
			 */
191 3035 perry
192 4080 daigle
			try {
193
194 4178 daigle
				String docTitlePath = PropertyService.getProperty("spatial.docTitle");
195 6595 leinfelder
				query = "select nodedata from xml_path_index where path = ?"
196
						+ " and docid = ?";
197 4080 daigle
				pstmt = dbconn.prepareStatement(query);
198 6595 leinfelder
				pstmt.setString(1, docTitlePath.trim());
199
				pstmt.setString(2, docid.trim());
200 4080 daigle
				pstmt.execute();
201
				rs = pstmt.getResultSet();
202
				if (rs.next())
203
					this.title = rs.getString(1);
204
				rs.close();
205
				pstmt.close();
206
			} catch (Exception e) {
207
				log.error(" **** Error getting docids from getTitle for docid = "
208
								+ docid);
209
				e.printStackTrace();
210
				this.title = docid;
211
			}
212
		}
213
214 3035 perry
  }
215
216 4080 daigle
  /**
217
	 * Returns a geotools (multi)polygon feature with geometry plus attributes
218
	 * ready to be inserted into our spatial dataset cache
219
	 */
220 5829 leinfelder
  public SimpleFeature getPolygonFeature() {
221 3035 perry
      // Get polygon feature type
222 5829 leinfelder
      SimpleFeatureType polyType = featureSchema.getPolygonFeatureType();
223 3035 perry
224
      MultiPolygon theGeom = getPolygonGeometry();
225
      if (theGeom == null)
226
          return null;
227
228
      // Populate the feature schema
229
      try {
230 5829 leinfelder
    	  SimpleFeatureBuilder builder = new SimpleFeatureBuilder(polyType);
231
    	  builder.addAll(
232
    			  new Object[]{
233
	                  theGeom,
234
	                  this.docid,
235
	                  getUrl(this.docid),
236
	                  this.title }
237
    			  );
238
          SimpleFeature polyFeature = builder.buildFeature(this.docid);
239 3035 perry
          return polyFeature;
240 5829 leinfelder
      } catch (Exception e) {
241
          log.error("Problem getting polygon feature: " + e.getMessage());
242 3035 perry
          return null;
243
      }
244
  }
245
246 3040 perry
  /**
247
   * Returns a geotools (multi)point feature with geometry plus attributes
248 3035 perry
   * ready to be inserted into our spatial dataset cache
249 3040 perry
   *
250 3035 perry
   */
251 5829 leinfelder
  public SimpleFeature getPointFeature() {
252 3035 perry
      // Get polygon feature type
253 5829 leinfelder
      SimpleFeatureType pointType = featureSchema.getPointFeatureType();
254 3035 perry
255
      MultiPoint theGeom = getPointGeometry();
256
      if (theGeom == null)
257
          return null;
258
259
      // Populate the feature schema
260
      try {
261 5829 leinfelder
    	  SimpleFeatureBuilder builder = new SimpleFeatureBuilder(pointType);
262
    	  builder.addAll(
263
    			  new Object[]{
264
	                  theGeom,
265
	                  this.docid,
266
	                  getUrl(this.docid),
267
	                  this.title }
268
    			  );
269
          SimpleFeature pointFeature = builder.buildFeature(this.docid);
270 3035 perry
          return pointFeature;
271 5829 leinfelder
      } catch (Exception e) {
272
          log.error("Problem getting point feature: " + e.getMessage());
273 3035 perry
          return null;
274
      }
275
  }
276
277 3040 perry
  /**
278 3035 perry
   * Given a valid docid, return an appropriate URL
279
   * for viewing the metadata document
280 3040 perry
   *
281
   * @param docid The document id for which to construct the access url.
282 3035 perry
   */
283
  private String getUrl( String docid ) {
284 4080 daigle
     String docUrl = null;
285
     try {
286
    	 docUrl = SystemUtil.getServletURL()
287
                    + "?action=read&docid=" + docid
288
                    + "&qformat="
289 4091 daigle
                    + PropertyService.getProperty("application.default-style");
290 4080 daigle
     } catch (PropertyNotFoundException pnfe) {
291
    	 log.error("Could not get access url because of unavailable property: "
292
    			 + pnfe.getMessage());
293
     }
294 3035 perry
295
     return docUrl;
296
  }
297
298 3040 perry
299 3035 perry
  /**
300 3037 perry
   * Returns a mutlipolygon geometry representing the geographic coverage(s) of the document
301 3040 perry
   *
302 3035 perry
   */
303
  private MultiPolygon getPolygonGeometry() {
304
305
    PrecisionModel precModel = new PrecisionModel(); // default: Floating point
306
    GeometryFactory geomFac = new GeometryFactory( precModel, featureSchema.srid );
307
    Vector polygons = new Vector();
308 3070 perry
    float w;
309
    float s;
310
    float e;
311
    float n;
312 3035 perry
313
    if ( west.size() == south.size() && south.size() == east.size() && east.size() == north.size() ) {
314
        for (int i = 0; i < west.size(); i++) {
315
316 3070 perry
            w = ((Float)west.elementAt(i)).floatValue();
317
            s = ((Float)south.elementAt(i)).floatValue();
318
            e = ((Float)east.elementAt(i)).floatValue();
319
            n = ((Float)north.elementAt(i)).floatValue();
320 3035 perry
321
            // Check if it's actually a valid polygon
322 3037 perry
            if (  w == 0.0 && s == 0.0 && e == 0.0 && n == 0.0) {
323 3035 perry
                log.warn("        Invalid or empty coodinates ... skipping");
324
                continue;
325 3070 perry
            } else if( Float.compare(w, e) == 0 && Float.compare(n,s) == 0 ) {
326 3035 perry
                log.warn("        Point coordinates only.. skipping polygon generation");
327
                continue;
328
            }
329
330 3037 perry
            // Handle the case of crossing the dateline and poles
331 3054 perry
            // dateline crossing is valid
332
            // polar crossing is not ( so we swap north and south )
333 3037 perry
            // Assumes all coordinates are confined to -180 -90 180 90
334 3070 perry
            float dl = 180.0f;
335
            float _dl = -180.0f;
336 3037 perry
337
            if ( w > e && s > n ) {
338 3054 perry
                log.info( "Crosses both the dateline and the poles .. split into 2 polygons, swap n & s" );
339
                polygons.add( createPolygonFromBbox( geomFac,   w,   n, dl, s ) );
340
                polygons.add( createPolygonFromBbox( geomFac, _dl,   n,  e, s ) );
341 3037 perry
            } else if ( w > e ) {
342
                log.info( "Crosses the dateline .. split into 2 polygons" );
343
                polygons.add( createPolygonFromBbox( geomFac,   w, s, dl, n ) );
344
                polygons.add( createPolygonFromBbox( geomFac, _dl, s,  e, n ) );
345
            } else if ( s > n ) {
346 3054 perry
                log.info( "Crosses the poles .. swap north and south" );
347
                polygons.add( createPolygonFromBbox( geomFac, w, n, e, s ) );
348 3037 perry
            } else {
349
                // Just a standard polygon that fits nicely onto our flat earth
350
                polygons.add( createPolygonFromBbox( geomFac, w, s, e, n ) );
351
            }
352
353
354 3035 perry
        }
355
    } else {
356
       log.error(" *** Something went wrong.. your east,west,north and south bounding arrays are different sizes!");
357
    }
358
359
    if( polygons.size() > 0 ) {
360
       Polygon[] polyArray = geomFac.toPolygonArray( polygons );
361
       MultiPolygon multiPolyGeom= geomFac.createMultiPolygon( polyArray );
362
       return multiPolyGeom;
363
    } else {
364
       return null;
365
    }
366
367
  }
368
369 3037 perry
370 3040 perry
  /**
371
   * Returns a polygon given the four bounding box coordinates
372 3037 perry
   */
373 3070 perry
  private Polygon createPolygonFromBbox( GeometryFactory geomFac, float w, float s, float e, float n ) {
374 3037 perry
375
        Coordinate[] linestringCoordinates = new Coordinate[5];
376
377
        linestringCoordinates[0] = new Coordinate( w, s );
378
        linestringCoordinates[1] = new Coordinate( w, n );
379
        linestringCoordinates[2] = new Coordinate( e, n );
380
        linestringCoordinates[3] = new Coordinate( e, s );
381
        linestringCoordinates[4] = new Coordinate( w, s );
382
383
        return geomFac.createPolygon( geomFac.createLinearRing(linestringCoordinates), null);
384
  }
385
386
387 3040 perry
  /**
388
   * Returns a multipoint geometry represnting the geographic coverage(s) of the document
389
   *
390
   * @todo Handle the case of crossing the dateline and poles
391 3035 perry
   */
392
  private MultiPoint getPointGeometry() {
393
394
    PrecisionModel precModel = new PrecisionModel(); // default: Floating point
395
    GeometryFactory geomFac = new GeometryFactory( precModel, featureSchema.srid );
396 3070 perry
    float w;
397
    float s;
398
    float e;
399
    float n;
400 3035 perry
401
    PreparedStatement pstmt = null;
402
    ResultSet rs = null;
403
404
    Vector points = new Vector();
405
406
    if ( west.size() == south.size() && south.size() == east.size() && east.size() == north.size() ) {
407
        for (int i = 0; i < west.size(); i++) {
408
409 3070 perry
            w = ((Float)west.elementAt(i)).floatValue();
410
            s = ((Float)south.elementAt(i)).floatValue();
411
            e = ((Float)east.elementAt(i)).floatValue();
412
            n = ((Float)north.elementAt(i)).floatValue();
413 3050 perry
414 3035 perry
            // Check if it's actually a valid point
415 3070 perry
            if (  w == 0.0f && s == 0.0f && e == 0.0f && n == 0.0f) {
416 3050 perry
                 log.warn("        Invalid or empty coodinates ... skipping");
417
                 continue;
418
            }
419 3035 perry
420 3070 perry
            float xCenter;
421
            float yCenter;
422 3050 perry
423
            // Handle the case of crossing the dateline and poles
424
            // Assumes all coordinates are confined to -180 -90 180 90
425
426 3054 perry
            if ( w > e ) {
427 3050 perry
                log.info( "Crosses the dateline .. " );
428 3070 perry
                xCenter = (360.0f - w + e)/ 2.0f + w;
429
                if( xCenter > 180.0f )
430
                    xCenter = xCenter - 360.0f;
431
                yCenter = ( s + n ) / 2.0f;
432 3050 perry
            } else {
433
                // Just a standard point that can be calculated by the average coordinates
434 3070 perry
                xCenter = ( w + e ) / 2.0f;
435
                yCenter = ( s + n ) / 2.0f;
436 3035 perry
            }
437
438
            points.add( geomFac.createPoint( new Coordinate( xCenter, yCenter)) );
439
        }
440
    } else {
441
       log.error(" *** Something went wrong.. your east,west,north and south bounding vectors are different sizes!");
442
    }
443
444
    if( points.size() > 0 ) {
445
       Point[] pointArray = geomFac.toPointArray( points );
446
       MultiPoint multiPointGeom= geomFac.createMultiPoint( pointArray );
447
       return multiPointGeom;
448
    } else {
449
       return null;
450
    }
451
452
453
  }
454
}