/* 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;
}
}