/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.svg;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.logging.Logger;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.WebMap;
import org.geotools.data.DataUtilities;
import org.geotools.data.Query;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.spatial.BBOX;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;
/**
* Streaming SVG encoder (does not support styling)
*
* @author Gabriel Roldan
* @version $Id$
*/
public class StreamingSVGMap extends WebMap {
private static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.vfny.geoserver.responses.wms.map");
/** the XML and SVG header */
private static final String SVG_HEADER = "<?xml version=\"1.0\" standalone=\"no\"?>\n\t"
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n\tstroke=\"green\" \n\tfill=\"none\" \n\tstroke-width=\"0.1%\"\n\tstroke-linecap=\"round\"\n\tstroke-linejoin=\"round\"\n\twidth=\"_width_\" \n\theight=\"_height_\" \n\tviewBox=\"_viewBox_\" \n\tpreserveAspectRatio=\"xMidYMid meet\">\n";
/** the SVG closing element */
private static final String SVG_FOOTER = "</svg>\n";
private SVGWriter writer;
/**
* Creates a new EncodeSVG object.
*
* @param mapContent
*
*/
public StreamingSVGMap(WMSMapContent mapContent) {
super(mapContent);
}
public void encode(final OutputStream out) throws IOException {
Envelope env = this.mapContent.getRenderingArea();
this.writer = new SVGWriter(out, mapContent.getRenderingArea());
writer.setMinCoordDistance(env.getWidth() / 1000);
long t = System.currentTimeMillis();
writeHeader();
writeLayers();
writer.write(SVG_FOOTER);
this.writer.flush();
t = System.currentTimeMillis() - t;
LOGGER.info("SVG generated in " + t + " ms");
}
public String createViewBox() {
Envelope referenceSpace = mapContent.getRenderingArea();
String viewBox = writer.getX(referenceSpace.getMinX()) + " "
+ (writer.getY(referenceSpace.getMinY()) - referenceSpace.getHeight()) + " "
+ referenceSpace.getWidth() + " " + referenceSpace.getHeight();
return viewBox;
}
private void writeHeader() throws IOException {
// TODO: this does not write out the doctype definition, there should be
// a configuration option wether to include it or not.
String viewBox = createViewBox();
String header = SVG_HEADER.replaceAll("_viewBox_", viewBox);
header = header.replaceAll("_width_", String.valueOf(mapContent.getMapWidth()));
header = header.replaceAll("_height_", String.valueOf(mapContent.getMapHeight()));
writer.write(header);
}
private void writeDefs(SimpleFeatureType layer) throws IOException {
GeometryDescriptor gtype = layer.getGeometryDescriptor();
Class geometryClass = gtype.getType().getBinding();
if ((geometryClass == MultiPoint.class) || (geometryClass == Point.class)) {
writePointDefs();
}
}
private void writePointDefs() throws IOException {
writer.write("<defs>\n\t<circle id='point' cx='0' cy='0' r='0.25%' fill='blue'/>\n</defs>\n");
}
/**
*
*
* @task TODO: respect layer filtering given by their Styles
*/
private void writeLayers() throws IOException {
List<Layer> layers = mapContent.layers();
int nLayers = layers.size();
// FeatureTypeInfo layerInfo = null;
int defMaxDecimals = writer.getMaximunFractionDigits();
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
for (int i = 0; i < nLayers; i++) {
Layer layer = layers.get(i);
SimpleFeatureIterator featureReader = null;
SimpleFeatureSource fSource;
fSource = (SimpleFeatureSource) layer.getFeatureSource();
SimpleFeatureType schema = fSource.getSchema();
try {
String defaultGeometry = schema.getGeometryDescriptor().getName().getLocalPart();
ReferencedEnvelope renderingArea = mapContent.getRenderingArea();
BBOX bboxFilter = ff.bbox(ff.property(defaultGeometry), renderingArea);
Query bboxQuery = new Query(schema.getTypeName(), bboxFilter);
Query definitionQuery = layer.getQuery();
Query finalQuery = new Query(DataUtilities.mixQueries(definitionQuery, bboxQuery,
"svgEncoder"));
finalQuery.setHints(definitionQuery.getHints());
finalQuery.setSortBy(definitionQuery.getSortBy());
finalQuery.setStartIndex(definitionQuery.getStartIndex());
LOGGER.fine("obtaining FeatureReader for " + schema.getTypeName());
featureReader = fSource.getFeatures(finalQuery).features();
LOGGER.fine("got FeatureReader, now writing");
String groupId = null;
String styleName = null;
groupId = schema.getTypeName();
styleName = layer.getStyle().getName();
writer.write("<g id=\"" + groupId + "\"");
if (!styleName.startsWith("#")) {
writer.write(" class=\"" + styleName + "\"");
}
writer.write(">\n");
writeDefs(schema);
writer.writeFeatures(fSource.getSchema(), featureReader, styleName);
writer.write("</g>\n");
} catch (IOException ex) {
throw ex;
} catch (Throwable t) {
LOGGER.warning("UNCAUGHT exception: " + t.getMessage());
IOException ioe = new IOException("UNCAUGHT exception: " + t.getMessage());
ioe.setStackTrace(t.getStackTrace());
throw ioe;
} finally {
if (featureReader != null) {
featureReader.close();
}
}
}
}
}