/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.wfs.response.dxf; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.text.NumberFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.geoserver.wfs.response.dxf.util.JulianDate; import org.geotools.feature.FeatureCollection; import org.geotools.geometry.jts.ReferencedEnvelope; /** * Basic, abstract implementation of DXFWriter. * Implements a common base of export functions useful for * any implementations (groups, tables, etc.). * @author Mauro Bartolomeoli, mbarto@infosia.it * */ public abstract class AbstractDXFWriter implements DXFWriter { // physical writer onto which the dxf will be written protected Writer writer = null; // numeric format used for real numbers (expecially coordinates) protected NumberFormat format = null; // numeric format used for julian dates protected NumberFormat dateFormat = null; // numeric format used for real numbers in ltype definition protected NumberFormat ltypeFormat = null; // end of line marker protected String EOL = "\n"; // default encoding protected String encoding = "ANSI_1252"; // flag to choose if all geometries should be written as blocks // (faster) or only when they are too complex (slower) protected boolean geometryAsBlock = false; // array of cyclical used colors (specified as autocad color indexes) // each color is assigned to a layer until there are elements // available, then they are reused again protected int[] colors = new int[] { 7, 1, 2, 3, 4, 5, 6, 8, 9 }; // array of cyclical used line types protected LineType[] lineTypes = new LineType[] { new LineType("CONTINUOUS", "Solid line") }; // current layer color (index in the colors array) private int colorPos = 0; // current layer line type (index in the lineTypes array) private int ltypePos = 0; // list of names to be assigned to export layers private String[] layerNames = null; // counter used to name layers, if no layer name list is given // as an option int layerCounter = 0; // assigned names (id -> name), used to reference layers // in different steps of the dxf writing private Map<String, String> cachedNames = new HashMap<String, String>(); // cached global envelope private ReferencedEnvelope e = null; // handle counters // each type of object needs a separate handle space, to // avoid conflicts, so we mantain a set of counters // for different kink of objects Map<String, Integer> handles = new HashMap<String, Integer>(); /** * Create a new instance of the writer, using the given underlying writer. * * @param writer * @return */ public abstract DXFWriter newInstance(Writer writer); /** * Verifies if the writer supports the request dxf version. * * @param version * @return */ public boolean supportsVersion(String version) { if (version == null) return true; return false; } public AbstractDXFWriter() { } /** * Full constructor. Needs a writer, to write the dxf out. It permits to specify an encoding for * the dxf. * * @param writer * @param encoding */ public AbstractDXFWriter(Writer writer, String encoding) { // initialize handle counters handles.put("LType", 22); // max 19 linetypes handles.put("VPort", 41); // max 5 vports handles.put("Layer", 46); // max 154 layers handles.put("BlockRecord", 200); // max 299800 blocks handles.put("Block", 300000); handles.put("Geometry", 600000); // max 395075 geometries this.writer = writer; if (encoding != null) this.encoding = encoding; // initialize number formats format = NumberFormat.getInstance(Locale.US); format.setMaximumFractionDigits(2); format.setGroupingUsed(false); format.setMinimumFractionDigits(1); ltypeFormat = NumberFormat.getInstance(Locale.US); ltypeFormat.setMaximumFractionDigits(4); ltypeFormat.setGroupingUsed(false); ltypeFormat.setMinimumFractionDigits(1); dateFormat = NumberFormat.getInstance(Locale.US); dateFormat.setMaximumFractionDigits(10); dateFormat.setGroupingUsed(false); dateFormat.setMinimumFractionDigits(1); } /** * Simple constructor. Needs a writer, to write the dxf out. * * @param writer * @param encoding */ public AbstractDXFWriter(Writer writer) { this(writer, null); } /** * Performs the actual writing. * Override it in the actual implementation class. * * @param featureList * @param version * @throws IOException */ public abstract void write(List featureList, String version) throws IOException; /** * Extracts and cache the global ReferenceEnvelope for the given feature list. * * @param featureList * @return */ protected ReferencedEnvelope getEnvelope(List featureList) { if (e == null) { for (int i = 0; i < featureList.size(); i++) { FeatureCollection collection = (FeatureCollection) featureList.get(i); if (e == null) { e = collection.getBounds(); } else { e.expandToInclude(collection.getBounds()); } } } return normalizeEnvelope(e); } /** * Normalizes an envelope to get a usable viewport. * * @param e2 * @return */ private ReferencedEnvelope normalizeEnvelope(ReferencedEnvelope e) { if (e != null) { // if it's empty, get a 1 meter envelope around it if (e.getWidth() == 0 && e.getHeight() == 0) { e.expandBy(1); } } return e; } /** * Writes the simplest dxf object, a group, composed of a numeric code and a value. The value * type can be interpreted looking at the code. * * @param code * @param value * @throws IOException */ protected void writeGroup(int code, String value) throws IOException { writer.write(StringUtils.leftPad(code + "", 3) + EOL); writer.write(value + EOL); } /** * Writes the End of file group. * * @throws IOException */ protected void writeEof() throws IOException { writeStart("EOF"); } /** * Writes a start (section, etc.) group. * * @param entity * @throws IOException */ protected void writeStart(String entity) throws IOException { writeGroup(0, entity); } /** * Loads a static section from a resource/file. * Some parts of the dxf can be really static, so it's * easier to load them from files. * * @param resource * @throws IOException */ protected void loadFromResource(String resource) throws IOException { final InputStream tpl = this.getClass().getClassLoader().getResourceAsStream( resource + ".dxf"); if (tpl != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(tpl)); String line = null; while ((line = reader.readLine()) != null) writer.write(line + EOL); } } /** * Writes a section start. * * @param name * @throws IOException */ protected void writeSectionStart(String name) throws IOException { writeStart("SECTION"); writeName(name); } /** * Writes a section end. * * @param name * @throws IOException */ protected void writeSectionEnd() throws IOException { writeGroup(0, "ENDSEC"); } /** * Writes a table start. * * @param name * @throws IOException */ protected void writeTableStart(String table) throws IOException { writeGroup(0, "TABLE"); writeName(table); } /** * Writes a table end. * * @param name * @throws IOException */ protected void writeTableEnd() throws IOException { writeGroup(0, "ENDTAB"); } /** * Writes an handle of the given type. The type is used to generate handles in different numeric * spaces, for different entities. * * @param name * @throws IOException */ protected String writeHandle(String type) throws IOException { String handle = getNewHandle(type); writeGroup(5, handle); return handle; } /** * Gets a new handle of the given type. The type is used to generate handles in different * numeric spaces, for different entities. * * @param name * @throws IOException */ protected String getNewHandle(String type) { int currentHandle = handles.get(type); String handle = Integer.toHexString(currentHandle).toUpperCase(); currentHandle++; handles.put(type, currentHandle); return handle; } /** * Gets a name for the layer represented by the given collection. * * @param coll * @return */ protected String getLayerName(FeatureCollection coll) { String name = getCachedName(coll.hashCode() + ""); if (name == null) { name = layerNames[layerCounter]; if (name.equals("")) name = "LAYER" + layerCounter; layerCounter++; storeCachedName(coll.hashCode() + "", name); } return name; } /** * Store a layer name for future use. * @param id * @param name */ private void storeCachedName(String id, String name) { cachedNames.put(id, name); } /** * Gets a stored layer name. * @param id * @return */ private String getCachedName(String id) { return cachedNames.get(id); } /** * Assign a color to the collection, cycling through * the available color list. * @param coll * @return */ protected int getColor(FeatureCollection coll) { int color = colors[colorPos]; if (colorPos < (colors.length - 1)) colorPos++; else colorPos = 0; return color; } /** * Assign a line type to the collection, cycling through * the available line types list. * @param coll * @return */ protected int getLineType(FeatureCollection coll) { int ltype = ltypePos; if (ltypePos < (lineTypes.length - 1)) ltypePos++; else ltypePos = 0; return ltype; } /** * Writes a layer group. * * @param layer * @throws IOException */ protected void writeLayer(String layer) throws IOException { writeGroup(8, layer); } /** * Writes an xref path group. * * @param path * @throws IOException */ protected void writePath(String path) throws IOException { writeGroup(1, path); } /** * Writes a geometry start, for the given geometry name (line, etc.). The geometry belongs to * the given layer and has an owner handle. The geometry is assigned a line type and color, if * specified. * * @param geometryName * @param layer * @param ownerHandle * @throws IOException */ protected void writeGeometryStart(String geometryName, String layer, String ownerHandle, int lineType, int color) throws IOException { writeGroup(0, geometryName); writeHandle("Geometry"); if (ownerHandle != null) writeOwnerHandle(ownerHandle); writeSubClass("AcDbEntity"); writeLayer(layer); if (lineType != -1) writeLineType(lineType); if (color != -1) writeColor(7); } /** * Writes a geometry start, for the given geometry name (line, etc.). The geometry belongs to * the given layer and has an owner handle. The geometry is assigned layer line type and color. * * @param geometryName * @param layer * @param ownerHandle * @throws IOException */ protected void writeGeometryStart(String geometryName, String layer, String ownerHandle) throws IOException { writeGeometryStart(geometryName, layer, ownerHandle, -1, -1); } /** * Writes a color group. * * @param color * @throws IOException */ protected void writeColor(int color) throws IOException { writeIntegerGroup(62, color); } /** * Writes a line type group. * * @param lineType * @throws IOException */ protected void writeLineType(int lineType) throws IOException { writeGroup(6, lineTypes[lineType].getName()); } /** * Writes an owner handle group (assigns an owner to the object via its handle). * * @param handle * @throws IOException */ protected void writeOwnerHandle(String handle) throws IOException { writeGroup(330, handle); } /** * Writes a subclass marker group. * * @param subclass * @throws IOException */ protected void writeSubClass(String subclass) throws IOException { writeGroup(100, subclass); } /** * Writes a size (number of following objects) group. * * @param size * @throws IOException */ protected void writeSize(int size) throws IOException { writeIntegerGroup(70, size); } /** * Writes a name group. * * @param name * @throws IOException */ protected void writeName(String name) throws IOException { writeGroup(2, name); } /** * Writes an header variable. * * @param varName * @throws IOException */ protected void writeVariable(String varName) throws IOException { writeGroup(9, "$" + varName); } /** * Writes a group having an integer value. * * @param code * @param value * @throws IOException */ protected void writeIntegerGroup(int code, int value) throws IOException { writeGroup(code, StringUtils.leftPad(value + "", 6)); } protected void writeFlags(int code, int value) throws IOException { writeGroup(code, StringUtils.leftPad(value + "", 9)); } /** * Writes a point with the given coordinates. Use NaN to exclude a coordinate (tipically z) from * output. Uses the standard (10,20,30) codes. * * @param x * @param y * @param z * @throws IOException */ protected void writePoint(double x, double y, double z) throws IOException { writePoint(10, x, y, z); } /** * Writes a point with the given coordinates. Use NaN to exclude a coordinate (tipically z) from * output. Uses the codes given by baseCode (baseCode,baseCode+10,baseCode+20) * * @param x * @param y * @param z * @throws IOException */ protected void writePoint(int baseCode, double x, double y, double z) throws IOException { writeDoubleGroup(baseCode, x); writeDoubleGroup(baseCode + 10, y); if (!Double.isNaN(z)) writeDoubleGroup(baseCode + 20, z); } /** * Writes a group having a double value. * * @param code * @param value * @throws IOException */ protected void writeDoubleGroup(int code, double val) throws IOException { writeGroup(code, format.format(val)); } /** * Writes a group having a double value. * * @param code * @param value * @throws IOException */ protected void writeLength(int code, double val) throws IOException { writeGroup(code, ltypeFormat.format(val)); } /** * Writes a group representing a date in julian format. * * @param dt * @throws IOException */ protected void writeJulianDate(Date dt) throws IOException { writeGroup(40, dateFormat.format(JulianDate.toJulian(dt))); } /** * Configure an option (usually got as a format option). */ public void setOption(String optionName, Object optionValue) { if (optionName.equalsIgnoreCase("geometryasblock")) setGeometryAsBlock(((Boolean) optionValue).booleanValue()); if (optionName.equalsIgnoreCase("colors")) setColors((int[]) optionValue); if (optionName.equalsIgnoreCase("linetypes")) setLineTypes((LineType[]) optionValue); if (optionName.equalsIgnoreCase("layers")) setLayerNames((String[]) optionValue); } /** * Sets the "all geometries as blocks" flag. * * @param geometryAsBlock */ public void setGeometryAsBlock(boolean geometryAsBlock) { this.geometryAsBlock = geometryAsBlock; } /** * Set custom array of colors to assign to written layers. * * @param colors */ public void setColors(int[] colors) { this.colors = colors; } /** * Set custom array of line types to assign to written layers. * * @param colors */ public void setLineTypes(LineType[] lineTypes) { this.lineTypes = lineTypes; } /** * Set list of names to be used for layers. * @param layerNames */ public void setLayerNames(String[] layerNames) { this.layerNames = layerNames; } }