/* (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.vfny.geoserver.wms.responses.map.htmlimagemap; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.WMSMapContent; import org.geotools.data.DataSourceException; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.operation.DefaultMathTransformFactory; import org.geotools.referencing.operation.matrix.GeneralMatrix; import org.geotools.renderer.lite.RendererUtilities; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Graphic; import org.geotools.styling.LineSymbolizer; import org.geotools.styling.Mark; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.SLD; import org.geotools.styling.Style; import org.geotools.styling.Symbolizer; import org.geotools.styling.TextSymbolizer; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.filter.expression.Expression; import org.opengis.referencing.FactoryException; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.vfny.geoserver.wms.responses.map.htmlimagemap.holes.HolesRemover; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Encodes a layer in HTMLImageMap format. * * @author Mauro Bartolomeoli */ public class HTMLImageMapWriter extends OutputStreamWriter { private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(HTMLImageMapWriter.class.getPackage().getName()); GeometryFactory gFac=new GeometryFactory(); /** map of geometry class to writer */ private Map<Class<?>,HTMLImageMapFeatureWriter> writers; WMSMapContent mapContent=null; /** rect representing screen coordinates space **/ Rectangle mapArea=null; ReferencedEnvelope mapEnv=null; Polygon clippingBox=null; /** * Transformation from layer (world) coordinates to "screen" coordinates. */ private AffineTransform worldToScreen=null; /** * Creates a new HTMLImageMapWriter object. * * @param out stream to encode the layer to * @param config current wms context * @throws ClassCastException * @throws UnsupportedEncodingException */ public HTMLImageMapWriter(OutputStream out, WMSMapContent mapContent) throws UnsupportedEncodingException, ClassCastException { super(out, guessCharset(mapContent)); this.mapContent=mapContent; mapEnv = mapContent.getRenderingArea(); clippingBox=envToGeometry(mapEnv); mapArea=new Rectangle(mapContent.getMapWidth(),mapContent.getMapHeight()); worldToScreen=RendererUtilities.worldToScreenTransform(mapEnv, mapArea); initWriters(); } private static String guessCharset(WMSMapContent mapContent) { GetMapRequest request = mapContent.getRequest(); if (request != null && request.getRequestCharset() != null) { String requestCharset = request.getRequestCharset(); return requestCharset; } return "UTF-8"; } private Polygon envToGeometry(ReferencedEnvelope env) { Coordinate[] coordinates=new Coordinate[] { new Coordinate(env.getMinX(),env.getMinY()), new Coordinate(env.getMaxX(),env.getMinY()), new Coordinate(env.getMaxX(),env.getMaxY()), new Coordinate(env.getMinX(),env.getMaxY()), new Coordinate(env.getMinX(),env.getMinY()) }; LinearRing bbox=gFac.createLinearRing(coordinates); return gFac.createPolygon(bbox, new LinearRing[] {}); } /** * Initializes every type of writer (one for every kind of geometry). * */ private void initWriters() { writers = new HashMap<Class<?>,HTMLImageMapFeatureWriter>(); writers.put(Point.class, new PointWriter()); writers.put(LineString.class, new LineStringWriter()); writers.put(LinearRing.class, new LineStringWriter()); writers.put(Polygon.class, new PolygonWriter()); writers.put(MultiPoint.class, new MultiPointWriter()); writers.put(MultiLineString.class, new MultiLineStringWriter()); writers.put(MultiPolygon.class, new MultiPolygonWriter()); writers.put(GeometryCollection.class, new GeometryCollectionWriter()); } /** * Encodes a newline * * @throws IOException if an error occurs during encoding public void newline() throws IOException { super.write('\n'); }*/ /** * Encodes a single layer (FeatureCollection) using the supplied style. * * @param fColl layer to encode * @param style style to use for encoding * @throws IOException if an error occurs during encoding * @throws AbortedException if the operation is aborted */ public void writeFeatures(SimpleFeatureCollection fColl, FeatureTypeStyle[] ftsList) throws IOException, AbortedException { SimpleFeature ft; SimpleFeatureIterator iter=null; try { SimpleFeatureType featureType = fColl.getSchema(); Class<?> gtype = featureType.getGeometryDescriptor().getType().getBinding(); // iterates through the single features iter=fColl.features(); while (iter.hasNext()) { ft = iter.next(); Geometry geo=(Geometry)ft.getDefaultGeometry(); if(!clippingBox.contains(geo)) { try { Geometry clippedGeometry=clippingBox.intersection(geo); ft.setDefaultGeometry(clippedGeometry); } catch (Throwable e) { // ignore and use the original geo } } // retrieves the right feature writer (based on the geometry type of the feature) HTMLImageMapFeatureWriter featureWriter = (HTMLImageMapFeatureWriter) writers.get(ft.getDefaultGeometry().getClass()); // encodes a single feature, using the supplied style and the current featureWriter featureWriter.writeFeature(ft,ftsList); ft = null; } LOGGER.fine("encoded " + featureType.getTypeName()); } catch (NoSuchElementException ex) { throw new DataSourceException(ex.getMessage(), ex); } finally { if(iter!=null) { //make sure we always close iter.close(); } } } /** * Filters the rules of <code>featureTypeStyle</code> returnting only * those that apply to <code>feature</code>. * <p> * This method returns rules for which: * <ol> * <li><code>rule.getFilter()</code> matches <code>feature</code>, or: * <li>the rule defines an "ElseFilter", and the feature matches no * other rules. * </ol> * This method returns an empty array in the case of which no rules * match. * </p> * @param featureTypeStyle The feature type style containing the rules. * @param feature The feature being filtered against. * */ Rule[] filterRules(FeatureTypeStyle featureTypeStyle, SimpleFeature feature) { Rule[] rules = featureTypeStyle.getRules(); if ((rules == null) || (rules.length == 0)) { return new Rule[0]; } List<Rule> filtered = new ArrayList<Rule>(rules.length); //process the rules, keep track of the need to apply an else filters boolean match = false; boolean hasElseFilter = false; for (int i = 0; i < rules.length; i++) { Rule rule = rules[i]; LOGGER.finer(new StringBuffer("Applying rule: ").append(rule.toString()).toString()); //does this rule have an else filter if (rule.hasElseFilter()) { hasElseFilter = true; continue; } /* double scaleDenominator; try { scaleDenominator = RendererUtilities.calculateScale(mapContent.getAreaOfInterest(), mapContent.getMapWidth(), mapContent.getMapHeight(),100); //is this rule within scale? if ( !EncodeHTMLImageMap.isWithInScale(rule,scaleDenominator)) { continue; } } catch (TransformException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FactoryException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ //does this rule have a filter which applies to the feature Filter filter = rule.getFilter(); if ((filter == null) || filter.evaluate(feature)) { match = true; filtered.add(rule); } } //if no rules mached the feautre, re-run through the rules applying // any else filters if (!match && hasElseFilter) { //loop through again and apply all the else rules for (int i = 0; i < rules.length; i++) { Rule rule = rules[i]; if (rule.hasElseFilter()) { filtered.add(rule); } } } return (Rule[]) filtered.toArray(new Rule[filtered.size()]); } /** * Base Class for all the feature writers. * An implementation is defined for every geometry type. * * @author Mauro Bartolomeoli */ private abstract class HTMLImageMapFeatureWriter { // stores a series of attributes to append to the feature tag definition Map<String,String> extraAttributes=new HashMap<String,String>(); StringBuffer buffer=new StringBuffer(); /** * Encodes a single feature. * Default implementation. * The encoding is accomplished through many phases: * 1) reset writer state * 2) process supplied style and apply filters to decide if the feature has to be included * in output. If the feature has to be included, proceed with the following phases, else go to the * next feature. * 3) start feature encoding * 4) pre geometry encoding * 5) actual geometry encoding * 6) post geometry encoding * 7) end feature encoding * @param ft feature to encode * @param style style to use for the encoding * @param fts "cached" ftss matching the FeatureType of the feature * @throws IOException if an error occurs during encoding */ protected void writeFeature(SimpleFeature ft,FeatureTypeStyle[] fts) throws IOException { // a new feature begins, reset accumulated info, such as extraAttributes reset(ft); // process the supplied style and store rendering info for the following phases // the style processing applies filters to the feature to decide if it has to be included // in output if(processStyle(ft,fts)) { try { Geometry geo=(Geometry)ft.getDefaultGeometry(); if(geo!=null) { // encodes starting element startElement(ft,""); // pre geometry encoding phase startGeometry(geo); // actual geometry encoding phase writeGeometry(geo,buffer); // post geometry encoding phase endGeometry(geo); // encodes ending element endElement(ft); // if everything has been correctly encoded, // we commit the buffer content to the stream commitBuffer(); } else { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("null geometry"); } } catch(IOException e) { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Problems encoding shape: "+e.getMessage()); } catch(Throwable t) { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.SEVERE)) LOGGER.severe("Problems encoding shape: "+t.getMessage()); } } } protected void commitBuffer() throws IOException { write(buffer.toString()); buffer=new StringBuffer(); } /** * Encodes a "MultiFeature", a feature with multiple geometries. * Default implementation. * The encoding is accomplished through many phases: * 1) reset writer state * 2) process supplied style and apply filters to decide if the feature has to be included * in output. If the feature has to be included, proceed with the following phases, else go to the * next feature. * 3) loops for all the geometries, with the following phases for every single geometry. * a) start feature encoding * b) pre geometry encoding * c) actual geometry encoding * d) post geometry encoding * e) end feature encoding * @param ft feature to encode * @param style style to use for the encoding * @param fts "cached" ftss matching the FeatureType of the feature * @throws IOException if an error occurs during encoding */ protected void writeMultiFeature(SimpleFeature ft,FeatureTypeStyle[] fts) throws IOException { reset(ft); if(processStyle(ft,fts)) { GeometryCollection geomCollection = (GeometryCollection) ft.getDefaultGeometry(); for (int i = 0; i < geomCollection.getNumGeometries(); i++) { try { Geometry geo=geomCollection.getGeometryN(i); if(geo!=null) { startElement(ft,"."+i); startGeometry(geo); writeGeometry(geo,buffer); endGeometry(geo); endElement(ft); commitBuffer(); } else { if(LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Problems encoding shape: null geometry"); } } catch(IOException e) { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Problems encoding shape: "+e.getMessage()); } catch(Throwable t) { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.SEVERE)) LOGGER.severe("Problems encoding shape: "+t.getMessage()); } } } } /** * Encodes the feature starting tag (area). * * @param feature feature to encode * @param suffix (optional) suffix to append to the tag id (useful to have different ids in * the multigeometry scenario. * * @throws IOException if an error occures during encoding */ protected void startElement(SimpleFeature feature,String suffix) throws IOException { // each feature (multi geometry ones are an exception) is represented by an <area> tag // each area tag has an id, equal to the feature id, and a shape (rect, poly or circle) writeToBuffer("<area shape=\""+getShape()+"\" id=\""+feature.getID()+suffix+"\" ",buffer); } /** * Encodes the geometry definition attribute (coords). * * @param geom geometry to encode * * @throws IOException if an error occures during encoding */ protected void startGeometry(Geometry geom) throws IOException { writeToBuffer(" coords=\"",buffer); } /** * Each area tag has a shape attribute, defining the coords attribute meaning. The W3C HTML 4.0 * standard defines 3 possible shape values: rect, poly and circle. * @return the writer associated shape value * @throws IOException if an error occures during encoding */ protected abstract String getShape() throws IOException; /** * Encodes the actual geometry. * * @param geom geometry to encode * * @throws IOException if an error occures during encoding */ protected abstract void writeGeometry(Geometry geom,StringBuffer buffer) throws IOException; /** * Encodes the geometry "ending" (closing quotes) * * @param geom geometry to encode * * @throws IOException if an error occures during encoding */ protected void endGeometry(Geometry geom) throws IOException { writeToBuffer("\"",buffer); } /** * Encodes feature ending. extraAttributes accumulated till now are encoded and the area tag is closed * * @param feature feature to encode * * @throws IOException if an error occures during encoding */ protected void endElement(SimpleFeature feature) throws IOException { Iterator<String> iter=extraAttributes.keySet().iterator(); while(iter.hasNext()) { String attrName=iter.next(); writeToBuffer(" "+attrName+"=\""+extraAttributes.get(attrName)+"\"",buffer); } writeToBuffer("/>\n",buffer); } /** * Resets writer status. * extraAttributes is emptied * @param ft current feature to encode */ protected void reset(SimpleFeature ft) { extraAttributes=new HashMap<String,String>(); buffer=new StringBuffer(); } /** * Analyze the supplied style and process any matching rule. * * @param ft feature to which the style is going to be applied * @param style style to process * @param ftsList cached fts matching the feature * @return true if the supplied feature has to be included in the output according to * style filters. * @throws IOException if an error occurs during the process */ protected boolean processStyle(SimpleFeature ft,FeatureTypeStyle[] ftsList) throws IOException { int total=0; for(int i=0;i<ftsList.length;i++) { FeatureTypeStyle fts=ftsList[i]; Rule[] rules=filterRules(fts, ft); total+=rules.length; for(int j=0;j<rules.length;j++) processRule(ft,rules[j]); } if(total==0) return false; return true; } /** * Process a single style rule and apply meaningful style to the feature * @param ft feature to which the rule has to be applied * @param rule rule to process * @throws IOException if an error occurs during the process */ protected void processRule(SimpleFeature ft,Rule rule) throws IOException { Symbolizer[] symbolizers=rule.getSymbolizers(); for(int i=0;i<symbolizers.length;i++) { Symbolizer symbolizer=symbolizers[i]; // process any given symbolizer processSymbolizer(ft,rule,symbolizer); } } /** * Process a single style symbolizer and apply meaningful style to the feature. * The default implementation processes TextSymbolizer, using Label definition to apply textual * attributes to the area tag. * @param ft feature to which the symbolizer has to be applied * @param rule current rule to analyze * @param symbolizer current symbolizer to analyze * @throws IOException if an error occurs during the process */ protected void processSymbolizer(SimpleFeature ft,Rule rule,Symbolizer symbolizer) throws IOException{ if(symbolizer instanceof TextSymbolizer) { // TODO: any check for label definition needed here? Expression e = SLD.textLabel((TextSymbolizer) symbolizer); // eval label actual value Object object = e.evaluate(ft); String value = null; if (object instanceof String) { value = (String) object; } else { if (object != null) { value = object.toString(); } } // if any label is defined append a new extra attribute // the attribute name is equal to the current rule name (if defined, defaults to "title") // the value of the attribute is the current label value if ((value != null) && !"".equals(value.trim())) { String attrName=rule.getName(); if(attrName==null || attrName.trim().equals("")) attrName="title"; extraAttributes.put(attrName,value); } } } /** * Gets a point in screen coordinates from a Coordinate in world coordinates * @param c projected coordinate * @return screen coordinates in textual form (x,y) */ protected String getPoint(Coordinate c) { Point2D transformed=worldToScreen.transform(new Point2D.Double(c.x,c.y),null); return (int)Math.round(transformed.getX())+","+(int)Math.round(transformed.getY()); } /** * Encodes a list of coordinates in textual form (for the coords attribute) * <i>coords</i> Area attribute<p> * * @param coords coordinates to encode * * @throws IOException if an error occurs during encoding */ protected void writePathContent(Coordinate[] coords,StringBuffer buf) throws IOException { StringBuffer tempBuf=new StringBuffer(); int nCoords = coords.length; for(int i=0;i<nCoords;i++) { Coordinate curr = coords[i]; String p=getPoint(curr); tempBuf.append(" "+p); } // Close the path if it's not already closed if(!coords[nCoords-1].equals2D(coords[0])) tempBuf.append(" "+coords[0].x+","+coords[0].y); if(tempBuf.length()>0) writeToBuffer(tempBuf.substring(1),buf); else throw new IOException("No coordinates"); } protected void writeToBuffer(String substring,StringBuffer buf) { buf.append(substring); } /** * Simplifies a geometry to exclude duplicated points. When translating from world * to screen coordinates it's possible that many world points collapse to a single screen point. * Those colliding points are simplified to a single point. * @param geom * */ Geometry decimate(Geometry geom) { DefaultMathTransformFactory f= new DefaultMathTransformFactory(); MathTransform xform=null; try { xform = f.createAffineTransform(new GeneralMatrix(worldToScreen.createInverse())); Decimator decimator=new Decimator(xform,mapArea); geom=decimator.decimate(geom); } catch (FactoryException e1) { } catch (NoninvertibleTransformException e1) { } catch (Exception e1) { } return geom; } } /** * FeatureWriter for point geometry features. * Currently supports circle WellKnownName Marks. */ private class PointWriter extends HTMLImageMapFeatureWriter { // size of the symbol double size = 2; /** * Creates a new PointWriter object. */ public PointWriter() { } /** * The shape for points is a circle. */ protected String getShape() throws IOException { return "circle"; } /** * Uses the supplied style to define point rendering. * Currently it gets WellKnownName from a Mark (circle is the only value correctly rendered by now). * It also uses the Size parameter to define circle radius. */ protected void processSymbolizer(SimpleFeature ft, Rule rule, Symbolizer symbolizer) throws IOException { super.processSymbolizer(ft, rule, symbolizer); if (symbolizer instanceof PointSymbolizer) { Mark mark = SLD.mark((PointSymbolizer) symbolizer); Graphic graphic = SLD.graphic((PointSymbolizer) symbolizer); if (graphic != null && mark != null) { Object oSize = graphic.getSize().evaluate(null); if (oSize != null) size = Double.parseDouble(oSize.toString()); } } } /** * Actually encodes the point. * * @param geom point to encode * * @throws IOException if an error occures during encoding */ protected void writeGeometry(Geometry geom, StringBuffer buf) throws IOException { if (geom instanceof Point) { Point p = (Point) geom; if (p.getCoordinate() != null) { writeToBuffer( getPoint(p.getCoordinate()) + "," + (int) Math.round(size), buf); } else throw new IOException("null point coordinate"); } else throw new IOException("Wrong geometry: it should be a Point"); } } /** * FeatureWriter for multipoint geometry features. */ private class MultiPointWriter extends PointWriter { /** * Creates a new MultiPointWriter object. */ public MultiPointWriter() { } /** * Uses writeMultiFeature. */ protected void writeFeature(SimpleFeature ft,FeatureTypeStyle[] fts) throws IOException { writeMultiFeature(ft, fts); } } /** * FeatureWriter for LineString geometry features. * A buffer is applied to the linear geometry to transform it to a Polygon. * The result polygon is then encoded. * @author Mauro Bartolomeoli * */ private class LineStringWriter extends HTMLImageMapFeatureWriter { // default buffer size (in screen coordinates) int buffer=2; /** * Creates a new LineStringWriter object. */ public LineStringWriter() { } /** * The shape for lines is a poly. */ protected String getShape() throws IOException { return "poly"; } /** * Uses the supplied style to define line rendering. * Currently it gets stroke-width to define the buffer around the linestring. */ protected void processSymbolizer(SimpleFeature ft, Rule rule,Symbolizer symbolizer) throws IOException{ super.processSymbolizer(ft, rule,symbolizer); if(symbolizer instanceof LineSymbolizer) { buffer=SLD.width((LineSymbolizer)symbolizer); } } /** * Actually encodes the linestring. * * @param geom line to encode * * @throws IOException if an error occures during encoding */ protected void writeGeometry(Geometry geom,StringBuffer buf) throws IOException { if(geom instanceof LineString) { LineString l = (LineString) geom; try { // transform buffer dimension to world coordinates double bufferMultiplier=worldToScreen.createInverse().getScaleX(); // gets buffered linestring Geometry buffered=l.buffer(buffer*bufferMultiplier); if(buffered instanceof Polygon) { Polygon poly=(Polygon)decimate(buffered); if(poly!=null) { LineString shell = poly.getExteriorRing(); if(shell!=null && shell.getCoordinates()!=null) writePathContent(shell.getCoordinates(),buf); else throw new IOException("Nothing to encode"); } else throw new IOException("Nothing to encode"); } else { throw new IOException("Impossible to encode: "+buffered); //TODO: what kind of geometry can a buffer operation // return? } } catch (NoninvertibleTransformException e) { throw new IOException(e.getMessage()); } } else throw new IOException("Wrong geometry: it should be a LineString"); } } /** * FeatureWriter for multiline geometry features. */ private class MultiLineStringWriter extends LineStringWriter { /** * Creates a new MultiLineStringWriter object. */ public MultiLineStringWriter() { } /** * Uses writeMultiFeature. */ protected void writeFeature(SimpleFeature ft,FeatureTypeStyle[] fts) throws IOException { writeMultiFeature(ft, fts); } } /** * FeatureWriter for Polygon geometry features. */ private class PolygonWriter extends HTMLImageMapFeatureWriter { /** * Creates a new PolygonWriter object. */ public PolygonWriter() { } /** * The shape for polygons is a poly. */ protected String getShape() throws IOException { return "poly"; } /** * Actually encodes the polygon. * * @param geom the polygon to encode * * @throws IOException if an error occures during encoding */ protected void writeGeometry(Geometry geom,StringBuffer buf) throws IOException { Polygon poly=null; if(geom instanceof Polygon) { poly=(Polygon)geom; // if we have any hole // we create a new polygon without holes // using the HolesRemover if(poly.getNumInteriorRing()>0) { poly=HolesRemover.removeHoles(poly,1.0/worldToScreen.getScaleX()); } poly=(Polygon)decimate(poly); } else throw new IOException("Impossible to encode: "+geom); if(poly!=null) { LineString shell = poly.getExteriorRing(); if(shell!=null && shell.getCoordinates()!=null) writePathContent(shell.getCoordinates(),buf); else throw new IOException("Nothing to encode"); } else throw new IOException("Nothing to encode"); } } /** * FeatureWriter for multipolygon geometry features. */ private class MultiPolygonWriter extends PolygonWriter { /** * Creates a new MultiPolygonWriter object. */ public MultiPolygonWriter() { } /** * Uses writeMultiFeature. */ protected void writeFeature(SimpleFeature ft,FeatureTypeStyle[] fts) throws IOException { writeMultiFeature(ft, fts); } } /** * FeatureWriter for multipolygon geometry features. */ private class GeometryCollectionWriter extends HTMLImageMapFeatureWriter { HTMLImageMapFeatureWriter delegateWriter=null; /** * Creates a new MultiPolygonWriter object. */ public GeometryCollectionWriter() { } /** * Encodes the GeometryCollection. * * The encoding is accomplished through many phases: * 1) reset writer state * 2) loops for all the geometries, with the following phases for every single geometry. * a) process supplied style * b) start feature encoding * c) pre geometry encoding * d) actual geometry encoding * e) post geometry encoding * f) end feature encoding * A delegate is used for many of these phases. The delegate is a specific FeatureWriter for the * single geometry, during the loop. * @param ft feature to encode * @param style style to use for the encoding * @param fts "cached" ftss matching the FeatureType of the feature * @throws IOException if an error occurs during encoding */ protected void writeFeature(SimpleFeature ft,FeatureTypeStyle[] fts) throws IOException { reset(ft); GeometryCollection geomCollection = (GeometryCollection) ft.getDefaultGeometry(); for (int i = 0; i < geomCollection.getNumGeometries(); i++) { Geometry geom=geomCollection.getGeometryN(i); if(geom!=null) { Class<?> gtype = geom.getClass(); // retrieves the right feature writer (based on the current geometry type) delegateWriter = (HTMLImageMapFeatureWriter) writers.get(gtype); if(processStyle(ft,fts)) { try { startElement(ft,"."+i); startGeometry(geom); writeGeometry(geom,buffer); endGeometry(geom); endElement(ft); commitBuffer(); } catch(IOException e) { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Problems encoding shape: "+e.getMessage()); } catch(Throwable t) { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.SEVERE)) LOGGER.severe("Problems encoding shape: "+t.getMessage()); } } } else { buffer=new StringBuffer(); if(LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Problems encoding shape: null geometry"); } } } protected String getShape() throws IOException { return delegateWriter.getShape(); } /** * Analyze the supplied style and process any matching rule. * * @param ft feature to which the style is going to be applied * @param style style to process * @param ftsList cached fts matching the feature * @return true if the style filters "accept" the feature * @throws IOException if an error occurs during the process */ protected boolean processStyle(SimpleFeature ft,FeatureTypeStyle[] ftsList) throws IOException { if(delegateWriter.processStyle(ft, ftsList)) { Iterator<String> iter=delegateWriter.extraAttributes.keySet().iterator(); while(iter.hasNext()) { String attrName=(String)iter.next(); extraAttributes.put(attrName,delegateWriter.extraAttributes.get(attrName)); } return true; } else return false; } /** * Actually write the geometry (through the delegate). */ protected void writeGeometry(Geometry geom,StringBuffer buf) throws IOException { if(geom!=null) delegateWriter.writeGeometry(geom,buf); else throw new IOException("null geometry"); } } }