/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.gml.producer;
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.Locale;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
//import org.geotools.feature.*;
/**
* Handles the writing of coordinates for GML.
*
* @author Chris Holmes
* @author Ian Schneider
* @source $URL$
*/
public class CoordinateWriter {
/**
* Internal representation of coordinate delimiter (',' for GML is default)
*/
private final String coordinateDelimiter;
/** Internal representation of tuple delimiter (' ' for GML is default) */
private final String tupleDelimiter;
/** To be used for formatting numbers, uses US locale. */
private final NumberFormat coordFormatter = NumberFormat.getInstance(Locale.US);
private final AttributesImpl atts = new org.xml.sax.helpers.AttributesImpl();
private final StringBuffer coordBuff = new StringBuffer();
private final FieldPosition zero = new FieldPosition(0);
private char[] buff = new char[200];
/**
* True of dummyZ should be used.
*/
private final boolean useDummyZ;
/** Dummy Z value (used to override coordinate.Z value) */
private final double dummyZ;
/** Dimension of expected coordinates */
private final int D;
/**
* Flag controlling wether namespaces should be ignored.
*/
private boolean namespaceAware = true;
/**
* Namepsace prefix + uri, default to gml
*/
private String prefix = "gml";
private String namespaceUri = GMLUtils.GML_URL;
/**
* The power of ten used for fast rounding
*/
private final double scale;
/**
* The min value at which the decimal notation is used
* (below it, the computerized scientific one is used instead)
*/
private static final double DECIMAL_MIN = Math.pow(10, -3);
/**
* The max value at which the decimal notation is used
* (above it, the computerized scientific one is used instead)
*/
private static final double DECIMAL_MAX = Math.pow(10, 7);
public CoordinateWriter() {
this(4);
}
public CoordinateWriter(int numDecimals, boolean isDummyZEnabled) {
this(numDecimals," ",",", isDummyZEnabled);
}
public CoordinateWriter(int numDecimals) {
this(numDecimals,false);
}
//TODO: check gml spec - can it be strings? Or just chars?
public CoordinateWriter(int numDecimals, String tupleDelim, String coordDelim){
this(numDecimals, tupleDelim, coordDelim, false);
}
public CoordinateWriter(int numDecimals, String tupleDelim, String coordDelim, boolean useDummyZ){
this(numDecimals, tupleDelim, coordDelim, useDummyZ, 0);
}
public CoordinateWriter(int numDecimals, String tupleDelim, String coordDelim, boolean useDummyZ, double zValue) {
this(numDecimals, tupleDelim, coordDelim, useDummyZ, 0, 2);
}
public CoordinateWriter(int numDecimals, boolean useDummyZ,
int dimension) {
this(numDecimals, " ", ",", useDummyZ, 0, dimension );
}
/**
* Create a CoordinateWriter for outputting GML coordinates.
* <p>
* The use of dimension, z and useZ is based on your needs:
* <ul>
* <li>dimension: is from your CoordinateReferenceSystem; it is the number of axis used by the coordinate
* <li>useZ: is used to force the use of 3 dimensions (if needed the z value below will be used for 2D data)
* <li>z: the dummy z value to use if the coordinate does not have one
* </ul>
*
* @param numDecimals Number of decimals to use (a speed vs accuracy trade off)
* @param tupleDelim delimiter to use between ordinates (usually ',')
* @param coordDelim delimiter to use between coordinates (usually ' ')
* @param useZ true if the provided zValue should be forced
* @param z Dummy z value to use if needed
* @param dimension Dimension of coordinates (usually 2 or 3)
*/
public CoordinateWriter(int numDecimals, String tupleDelim, String coordDelim, boolean useZ, double z, int dimension) {
if (tupleDelim == null || tupleDelim.length() == 0){
throw new IllegalArgumentException("Tuple delimeter cannot be null or zero length");
}
if ((coordDelim != null) && coordDelim.length() == 0) {
throw new IllegalArgumentException("Coordinate delimeter cannot be null or zero length");
}
D = dimension;
tupleDelimiter = tupleDelim;
coordinateDelimiter = coordDelim;
coordFormatter.setMaximumFractionDigits(numDecimals);
coordFormatter.setGroupingUsed(false);
scale = Math.pow(10, numDecimals);
String uri = namespaceUri;
if ( !namespaceAware ) {
uri = null;
}
atts.addAttribute(uri, "decimal", "decimal", "decimal", ".");
atts.addAttribute(uri, "cs", "cs", "cs",
coordinateDelimiter);
atts.addAttribute(uri, "ts", "ts", "ts", tupleDelimiter);
this.useDummyZ = useZ;
this.dummyZ = z;
}
public int getNumDecimals(){
return coordFormatter.getMaximumFractionDigits();
}
public boolean isDummyZEnabled(){
return useDummyZ;
}
public void setNamespaceAware(boolean namespaceAware) {
this.namespaceAware = namespaceAware;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public void setNamespaceUri(String namespaceUri) {
this.namespaceUri = namespaceUri;
}
/**
* Write the provided list of coordinates out.
* <p>
* There are a range of constants that control exactly what
* is written:
* <ul>
* <li>useDummyZ: if true dummyZ will be added to each coordiante
* <li>namespaceAware: is true the prefix and namespaceUri will be used
* <li>
* </ul>
*
* @param c
* @param output
* @throws SAXException
* @deprecated use #writeCoordinates(CoordinateSequence, ContentHandler) instead
*/
public void writeCoordinates(Coordinate[] c, ContentHandler output)
throws SAXException {
writeCoordinates(new CoordinateArraySequence(c), output);
}
/**
* Write the provided list of coordinates out.
* <p>
* There are a range of constants that control exactly what
* is written:
* <ul>
* <li>useDummyZ: if true dummyZ will be added to each coordiante
* <li>namespaceAware: is true the prefix and namespaceUri will be used
* <li>
* </ul>
*
* @param c
* @param output
* @throws SAXException
*/
public void writeCoordinates(CoordinateSequence c, ContentHandler output)
throws SAXException {
String prefix = this.prefix + ":";
String namespaceUri = this.namespaceUri;
if ( !namespaceAware ) {
prefix = "";
namespaceUri = null;
}
output.startElement(namespaceUri, "coordinates", prefix + "coordinates",
atts);
final int coordCount = c.size();
//used to check whether the coordseq handles a third dimension or not
final int coordSeqDimension = c.getDimension();
double x, y, z;
//write down a coordinate at a time
for (int i = 0, n = coordCount; i < n; i++) {
x = c.getOrdinate(i, 0);
y = c.getOrdinate(i, 1);
// clear the buffer
coordBuff.setLength(0);
// format x into buffer and append delimiter
formatDecimal(x, coordBuff);
coordBuff.append(coordinateDelimiter);
// format y into buffer
formatDecimal(y, coordBuff);
if (D == 3 || useDummyZ) {
z = (D == 3 && coordSeqDimension > 2)? c.getOrdinate(i, 2) : dummyZ;
coordBuff.append(coordinateDelimiter);
formatDecimal(z, coordBuff);
}
// if there is another coordinate, tack on a tuple delimiter
if (i + 1 < coordCount){
coordBuff.append(tupleDelimiter);
}
// make sure our character buffer is big enough
if (coordBuff.length() > buff.length) {
buff = new char[coordBuff.length()];
}
// copy the characters
coordBuff.getChars(0, coordBuff.length(), buff, 0);
// finally, output
output.characters(buff, 0, coordBuff.length());
}
output.endElement(namespaceUri,"coordinates", prefix + "coordinates");
}
private void formatDecimal(double x, StringBuffer sb) {
if(Math.abs(x) >= DECIMAL_MIN && x < DECIMAL_MAX) {
x = Math.floor(x * scale + 0.5) / scale;
long lx = (long) x;
if(lx == x)
sb.append(lx);
else
sb.append(x);
} else {
coordFormatter.format(x, coordBuff, zero);
}
}
}