001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.action;
006:
007: import java.io.File;
008: import java.io.IOException;
009: import java.util.ArrayList;
010: import java.util.Collections;
011: import java.util.Comparator;
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.Locale;
015:
016: import javax.servlet.ServletContext;
017: import javax.servlet.ServletException;
018: import javax.servlet.http.HttpServletRequest;
019: import javax.servlet.http.HttpServletResponse;
020:
021: import org.apache.struts.action.ActionForm;
022: import org.apache.struts.action.ActionForward;
023: import org.apache.struts.action.ActionMapping;
024: import org.apache.struts.action.DynaActionForm;
025: import org.apache.struts.util.MessageResources;
026: import org.geoserver.ows.util.RequestUtils;
027: import org.geoserver.platform.GeoServerExtensions;
028: import org.geotools.geometry.GeneralEnvelope;
029: import org.geotools.geometry.jts.ReferencedEnvelope;
030: import org.geotools.referencing.CRS;
031: import org.opengis.referencing.FactoryException;
032: import org.opengis.referencing.NoSuchAuthorityCodeException;
033: import org.opengis.referencing.crs.CoordinateReferenceSystem;
034: import org.opengis.referencing.operation.TransformException;
035: import org.vfny.geoserver.global.CoverageInfo;
036: import org.vfny.geoserver.global.Data;
037: import org.vfny.geoserver.global.FeatureTypeInfo;
038: import org.vfny.geoserver.global.GeoServer;
039: import org.vfny.geoserver.global.WMS;
040: import org.vfny.geoserver.util.requests.CapabilitiesRequest;
041: import org.vfny.geoserver.wms.servlets.Capabilities;
042:
043: import com.vividsolutions.jts.geom.Envelope;
044:
045: /**
046: * <b>MapPreviewAction</b><br> Sep 26, 2005<br>
047: * <b>Purpose:</b><br>
048: * Gathers up all the FeatureTypes in use and returns the informaion to the
049: * .jsp .<br>
050: * It will also generate requests to the WMS "openlayers" output format. <br>
051: * This will communicate to a .jsp and return it three arrays of strings that contain:<br>
052: * - The Featuretype's name<br>
053: * - The DataStore name of the FeatureType<br>
054: * - The bounding box of the FeatureType<br>
055: * To change what data is output to the .jsp, you must change <b>struts-config.xml</b>.<br>
056: * Look for the line:<br>
057: * <form-bean <br>
058: * name="mapPreviewForm"<br>
059: *
060: * @author Brent Owens (The Open Planning Project)
061: * @version
062: */
063: public class MapPreviewAction extends GeoServerAction {
064: // the layer is a coverage, or is made exclusively of coverages
065: public static final Integer LAYER_IS_COVERAGE = new Integer(0);
066: // the layer is a group with at least one coverage
067: public static final Integer LAYER_HAS_COVERAGE = new Integer(1);
068: // the layer or group is made of vectors only
069: public static final Integer LAYER_IS_VECTOR = new Integer(2);
070:
071: /* (non-Javadoc)
072: * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
073: */
074: public ActionForward execute(ActionMapping mapping,
075: ActionForm form, HttpServletRequest request,
076: HttpServletResponse response) throws IOException,
077: ServletException {
078: ArrayList dsList = new ArrayList();
079: ArrayList ftList = new ArrayList();
080: ArrayList bboxList = new ArrayList();
081: ArrayList srsList = new ArrayList();
082: ArrayList ftnsList = new ArrayList();
083: ArrayList widthList = new ArrayList();
084: ArrayList heightList = new ArrayList();
085: ArrayList coverageStatus = new ArrayList();
086:
087: // 1) get the capabilities info so we can find out our feature types
088: WMS wms = getWMS(request);
089: Capabilities caps = new Capabilities(wms);
090: CapabilitiesRequest capRequest = new CapabilitiesRequest("WMS",
091: caps);
092: capRequest.setHttpServletRequest(request);
093:
094: Data catalog = wms.getData();
095: List ftypes = new ArrayList(catalog.getFeatureTypeInfos()
096: .values());
097: for (Iterator it = ftypes.iterator(); it.hasNext();) {
098: FeatureTypeInfo ft = (FeatureTypeInfo) it.next();
099: if (!ft.isEnabled())
100: it.remove();
101: }
102: Collections.sort(ftypes, new FeatureTypeInfoNameComparator());
103:
104: List ctypes = new ArrayList(catalog.getCoverageInfos().values());
105: for (Iterator it = ctypes.iterator(); it.hasNext();) {
106: CoverageInfo ci = (CoverageInfo) it.next();
107: if (!ci.isEnabled())
108: it.remove();
109: }
110: Collections.sort(ctypes, new CoverageInfoNameComparator());
111:
112: List bmtypes = new ArrayList(wms.getBaseMapLayers().keySet());
113: Collections.sort(bmtypes);
114:
115: // 2) delete any existing generated files in the generation directory
116: ServletContext sc = request.getSession().getServletContext();
117:
118: //File rootDir = GeoserverDataDirectory.getGeoserverDataDirectory(sc);
119: //File previewDir = new File(sc.getRealPath("data/generated"));
120: if (sc.getRealPath("preview") == null) {
121: //There's a problem here, since we can't get a real path for the "preview" directory.
122: //On tomcat this happens when "unpackWars=false" is set for this context.
123: throw new RuntimeException(
124: "Couldn't populate preview directory...is the war running in unpacked mode?");
125: }
126:
127: File previewDir = new File(sc.getRealPath("preview"));
128:
129: //File previewDir = new File(rootDir, "data/generated");
130: if (!previewDir.exists()) {
131: previewDir.mkdirs();
132: }
133:
134: // We need to create a 4326 CRS for comparison to layer's crs
135: CoordinateReferenceSystem latLonCrs = null;
136:
137: try { // get the CRS object for lat/lon 4326
138: latLonCrs = CRS.decode("EPSG:" + 4326);
139: } catch (NoSuchAuthorityCodeException e) {
140: String msg = "Error looking up SRS for EPSG: " + 4326 + ":"
141: + e.getLocalizedMessage();
142: LOGGER.warning(msg);
143: } catch (FactoryException e) {
144: String msg = "Error looking up SRS for EPSG: " + 4326 + ":"
145: + e.getLocalizedMessage();
146: LOGGER.warning(msg);
147: }
148:
149: // 3) Go through each *FeatureType* and collect information && write out config files
150: for (Iterator it = ftypes.iterator(); it.hasNext();) {
151: FeatureTypeInfo layer = (FeatureTypeInfo) it.next();
152:
153: if (!layer.isEnabled() || layer.isGeometryless()) {
154: continue; // if it isn't enabled, move to the next layer
155: }
156:
157: CoordinateReferenceSystem layerCrs = layer.getDeclaredCRS();
158:
159: /* A quick and efficient way to grab the bounding box is to get it
160: * from the featuretype info where the lat/lon bbox is loaded
161: * from the DTO. We do this with layer.getLatLongBoundingBox().
162: * We need to reproject the bounding box from lat/lon to the layer crs
163: * for display
164: */
165: Envelope orig_bbox = layer.getLatLongBoundingBox();
166:
167: if ((orig_bbox.getWidth() == 0)
168: || (orig_bbox.getHeight() == 0)) {
169: orig_bbox.expandBy(0.1);
170: }
171:
172: ReferencedEnvelope bbox = new ReferencedEnvelope(orig_bbox,
173: latLonCrs);
174:
175: if (!CRS.equalsIgnoreMetadata(layerCrs, latLonCrs)) {
176: try { // reproject the bbox to the layer crs
177: bbox = bbox.transform(layerCrs, true);
178: } catch (TransformException e) {
179: e.printStackTrace();
180: } catch (FactoryException e) {
181: e.printStackTrace();
182: }
183: }
184:
185: // we now have a bounding box in the same CRS as the layer
186: if ((bbox.getWidth() == 0) || (bbox.getHeight() == 0)) {
187: bbox.expandBy(0.1);
188: }
189:
190: if (layer.isEnabled()) {
191: // prepare strings for web output
192: ftList.add(layer.getNameSpace().getPrefix() + "_"
193: + layer.getFeatureType().getTypeName()); // FeatureType name
194: ftnsList.add(layer.getNameSpace().getPrefix() + ":"
195: + layer.getFeatureType().getTypeName());
196: dsList.add(layer.getDataStoreInfo().getId()); // DataStore info
197: // bounding box of the FeatureType
198:
199: // expand bbox by 5% to allow large symbolizers to fit the map
200: bbox.expandBy(bbox.getWidth() / 20,
201: bbox.getHeight() / 20);
202: bboxList.add(bbox.getMinX() + "," + bbox.getMinY()
203: + "," + bbox.getMaxX() + "," + bbox.getMaxY());
204: srsList.add("EPSG:" + layer.getSRS());
205:
206: int[] imageBox = getMapWidthHeight(bbox);
207: widthList.add(String.valueOf(imageBox[0]));
208: heightList.add(String.valueOf(imageBox[1]));
209: }
210:
211: // not a coverage
212: coverageStatus.add(LAYER_IS_VECTOR);
213: }
214:
215: // 3.5) Go through each *Coverage* and collect its info
216: for (Iterator it = ctypes.iterator(); it.hasNext();) {
217: CoverageInfo layer = (CoverageInfo) it.next();
218:
219: // upper right corner? lower left corner? Who knows?! Better naming conventions needed guys.
220: double[] lowerLeft = layer.getEnvelope().getLowerCorner()
221: .getCoordinates();
222: double[] upperRight = layer.getEnvelope().getUpperCorner()
223: .getCoordinates();
224: Envelope bbox = new Envelope(lowerLeft[0], upperRight[0],
225: lowerLeft[1], upperRight[1]);
226:
227: if (layer.isEnabled()) {
228: // prepare strings for web output
229: String shortLayerName = layer.getName().split(":")[1]; // we don't want the namespace prefix
230:
231: ftList.add(layer.getNameSpace().getPrefix() + "_"
232: + shortLayerName); // Coverage name
233: ftnsList.add(layer.getNameSpace().getPrefix() + ":"
234: + shortLayerName);
235:
236: dsList.add(layer.getFormatInfo().getId()); // DataStore info
237: // bounding box of the Coverage
238:
239: bboxList.add(bbox.getMinX() + "," + bbox.getMinY()
240: + "," + bbox.getMaxX() + "," + bbox.getMaxY());
241: srsList.add(layer.getSrsName());
242:
243: int[] imageBox = getMapWidthHeight(bbox);
244: widthList.add(String.valueOf(imageBox[0]));
245: heightList.add(String.valueOf(imageBox[1]));
246:
247: // this layer is a coverage, all right
248: coverageStatus.add(LAYER_IS_COVERAGE);
249: }
250: }
251:
252: // 3.6) Go thru base map layers
253: Locale locale = (Locale) request.getLocale();
254: MessageResources messages = getResources(request);
255: String baseMap = messages.getMessage(locale, "label.baseMap");
256: for (Iterator it = bmtypes.iterator(); it.hasNext();) {
257: String baseMapTitle = (String) it.next();
258: ftList.add(baseMapTitle);
259: ftnsList.add(baseMapTitle);
260: dsList.add(baseMap);
261: GeneralEnvelope bmBbox = ((GeneralEnvelope) wms
262: .getBaseMapEnvelopes().get(baseMapTitle));
263: Envelope bbox = new Envelope(bmBbox.getMinimum(0), bmBbox
264: .getMaximum(0), bmBbox.getMinimum(1), bmBbox
265: .getMaximum(1));
266: bboxList.add(bbox.getMinX() + "," + bbox.getMinY() + ","
267: + bbox.getMaxX() + "," + bbox.getMaxY());
268: srsList.add(CRS.lookupIdentifier(bmBbox
269: .getCoordinateReferenceSystem(), null, false));
270: int[] imageBox = getMapWidthHeight(bbox);
271: widthList.add(String.valueOf(imageBox[0]));
272: heightList.add(String.valueOf(imageBox[1]));
273:
274: // this depends on the composition, we raise the flag if the layer has at least
275: // one coverage
276: coverageStatus.add(computeGroupCoverageStatus(wms,
277: baseMapTitle));
278: }
279:
280: // 4) send off gathered information to the .jsp
281: DynaActionForm myForm = (DynaActionForm) form;
282:
283: myForm.set("DSNameList", dsList.toArray(new String[dsList
284: .size()]));
285: myForm.set("FTNameList", ftList.toArray(new String[ftList
286: .size()]));
287: myForm.set("BBoxList", bboxList.toArray(new String[bboxList
288: .size()]));
289: myForm.set("SRSList", srsList
290: .toArray(new String[srsList.size()]));
291: myForm.set("WidthList", widthList.toArray(new String[widthList
292: .size()]));
293: myForm.set("HeightList", heightList
294: .toArray(new String[heightList.size()]));
295: myForm.set("FTNamespaceList", ftnsList
296: .toArray(new String[ftnsList.size()]));
297: myForm.set("CoverageStatus", coverageStatus
298: .toArray(new Integer[coverageStatus.size()]));
299: String proxifiedBaseUrl = RequestUtils.baseURL(request);
300: GeoServer gs = (GeoServer) GeoServerExtensions.extensions(
301: GeoServer.class).get(0);
302: proxifiedBaseUrl = RequestUtils.proxifiedBaseURL(
303: proxifiedBaseUrl, gs.getProxyBaseUrl());
304: myForm.set("BaseUrl", proxifiedBaseUrl);
305:
306: return mapping.findForward("success");
307: }
308:
309: /**
310: * Computes the coverage status flag for the specified layer
311: * @param baseMapTitle
312: * @return
313: */
314: private Integer computeGroupCoverageStatus(WMS wms,
315: String baseMapTitle) {
316: String layerParam = (String) wms.getBaseMapLayers().get(
317: baseMapTitle);
318: String[] layers = layerParam.split(",");
319: int coverageCount = 0;
320: for (int i = 0; i < layers.length; i++) {
321: if (wms.getData().getLayerType(layers[i]) == Data.TYPE_RASTER)
322: coverageCount++;
323: }
324: if (coverageCount == 0)
325: return LAYER_IS_VECTOR;
326: else if (coverageCount < layers.length)
327: return LAYER_HAS_COVERAGE;
328: else
329: return LAYER_IS_COVERAGE;
330: }
331:
332: private int[] getMapWidthHeight(Envelope bbox) {
333: int width;
334: int height;
335: double ratio = bbox.getHeight() / bbox.getWidth();
336:
337: if (ratio < 1) {
338: width = 750;
339: height = (int) Math.round(750 * ratio);
340: } else {
341: width = (int) Math.round(550 / ratio);
342: height = 550;
343: }
344:
345: // make sure we reach some minimal dimensions (300 pixels is more or less
346: // the height of the zoom bar)
347: if (width < 300) {
348: width = 300;
349: }
350:
351: if (height < 300) {
352: height = 300;
353: }
354:
355: // add 50 pixels horizontally to account for the zoom bar
356: return new int[] { width + 50, height };
357: }
358:
359: private static class FeatureTypeInfoNameComparator implements
360: Comparator {
361: public int compare(Object o1, Object o2) {
362: FeatureTypeInfo ft1 = (FeatureTypeInfo) o1;
363: FeatureTypeInfo ft2 = (FeatureTypeInfo) o2;
364: String ft1Name = ft1.getNameSpace().getPrefix()
365: + ft1.getName();
366: String ft2Name = ft2.getNameSpace().getPrefix()
367: + ft2.getName();
368:
369: return ft1Name.compareTo(ft2Name);
370: }
371: }
372:
373: private static class CoverageInfoNameComparator implements
374: Comparator {
375: public int compare(Object o1, Object o2) {
376: CoverageInfo c1 = (CoverageInfo) o1;
377: CoverageInfo c2 = (CoverageInfo) o2;
378: String ft1Name = c1.getNameSpace().getPrefix()
379: + c1.getName();
380: String ft2Name = c2.getNameSpace().getPrefix()
381: + c2.getName();
382:
383: return ft1Name.compareTo(ft2Name);
384: }
385: }
386: }
|