/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.commons.cismap.io.converters; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.PrecisionModel; import org.apache.log4j.Logger; import org.openide.util.NbBundle; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; import de.cismet.cismap.commons.CrsTransformer; import de.cismet.commons.converter.ConversionException; /** * Basic <code>TextToGeometryConverter</code> implementation that expects the given text to be separated by white space * characters, ';' or ':'. Additionally it requires a parameter: the EPSG code, e.g. EPSG:4326. The code is used to * interpret the given coordinates. The coordinates are expected to be provided as follows:<br/> * <br/> * northing<separator>easting<separator>northing<separator>easting<separator> ...<br/> * <br/> * * <p>The coordinates are parsed using the currently active {@link Locale} and thus the corresponding * {@link NumberFormat}. Three-dimensional coordinates are not supported.</p> * * @author martin.scholl@cismet.de * @version 1.0 */ public abstract class AbstractGeometryFromTextConverter extends AbstractRatingConverter<String, Geometry> implements TextToGeometryConverter { //~ Static fields/initializers --------------------------------------------- /** LOGGER. */ private static final transient Logger LOG = Logger.getLogger(AbstractGeometryFromTextConverter.class); public static final String SYS_PROP_DECIMAL_SEP; private static final char[] DEFAULT_TOKEN_SEPARATORS; private static final char[] WHITE_SPACE_CHARS; static { SYS_PROP_DECIMAL_SEP = "de.cismet.commons.cismap.io.convertes.AbstractGeometryFromTextConverter.decimalSeparator"; // NOI18N // in line with Character.isWithespace() WHITE_SPACE_CHARS = new char[] { // white space characters 0x20, // space 0x2028, // line sep 0x2029, // paragraph sep 0x09, // tab 0x0A, // LF 0x0B, // vertical tab 0x0C, // form feed 0x0D, // CR 0x1C, // file sep 0x1D, // group sep 0x1E, // record sep 0x1F // unit sep }; DEFAULT_TOKEN_SEPARATORS = new char[WHITE_SPACE_CHARS.length + 3]; DEFAULT_TOKEN_SEPARATORS[0] = 0x3A; // colon DEFAULT_TOKEN_SEPARATORS[1] = 0x3B; // semicolon DEFAULT_TOKEN_SEPARATORS[2] = 0x2C; // comma System.arraycopy(WHITE_SPACE_CHARS, 0, DEFAULT_TOKEN_SEPARATORS, 3, WHITE_SPACE_CHARS.length); } //~ Methods ---------------------------------------------------------------- /** * Creates a geometry from the given coordinate array and geometry factory. The coordinate array and the geomeetry * factory shall never be <code>null</code>. * * @param coordinates the coordinates to create a geometry from * @param geomFactory the geometry factory that may be used to create the geometry * * @return a geometry created using the given parameters * * @throws ConversionException if any error occurs during creation of the geometry */ protected abstract Geometry createGeometry(final Coordinate[] coordinates, final GeometryFactory geomFactory) throws ConversionException; // this is because of jalopy as for some reason it generates a javadoc template for this method although overridden /** * {@inheritDoc} */ @Override public Geometry convertForward(final String from, final String... params) throws ConversionException { if ((from == null) || from.isEmpty()) { throw new IllegalArgumentException("from must not be null or empty"); // NOI18N } if ((params == null) || (params.length < 1)) { throw new IllegalArgumentException("no parameters provided, epsgcode is required parameter"); // NOI18N } final String[] tokens = from.split(getTokenRegex()); // NOI18N if ((tokens.length % 2) == 0) { final int srid; try { srid = CrsTransformer.extractSridFromCrs(params[0]); } catch (final Exception e) { throw new ConversionException("unsupported epsg parameter: " + params[0], e); // NOI18Ny } final GeometryFactory geomFactory = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), srid); final Coordinate[] coordinates = new Coordinate[tokens.length / 2]; final NumberFormat format = getDecimalFormat(); for (int i = 0; i < tokens.length; i += 2) { try { final double easting = format.parse(tokens[i]).doubleValue(); final double northing = format.parse(tokens[i + 1]).doubleValue(); coordinates[i / 2] = new Coordinate(easting, northing); } catch (final ParseException ex) { throw new ConversionException("cannot parse convert data into valid double", ex); // NOI18N } } return createGeometry(coordinates, geomFactory); } else { throw new ConversionException("uneven number of tokens illegal, only two dimensional coordinates allowed"); // NOI18N } } @Override public String convertBackward(final Geometry to, final String... params) throws ConversionException { if (to == null) { throw new IllegalArgumentException("'to' must not be null"); // NOI18N } final StringBuilder sb = new StringBuilder(); final NumberFormat nf = getDecimalFormat(); for (final Coordinate coord : to.getCoordinates()) { sb.append(nf.format(coord.x)); sb.append(' '); sb.append(nf.format(coord.y)); sb.append('\n'); } return sb.toString(); } /** * Gets the decimal separator that is used by this converter. The separator is read from the system property * {@link #SYS_PROP_DECIMAL_SEP}. If the property is not set or is empty or is an invalid character (any white * space) or an invalid format the default separator of the default locale is returned. Supported formats are:<br/> * * <ul> * <li>those of {@link Integer#decode(java.lang.String)}</li> * <li>a string starting with '\u' or '\U' followed by a hexadecimal character code</li> * </ul> * <br/> * If none of these formats is used simply the first char of the given string is returned. * * @return the decimal separator to be used by this converter * * @see Character#isWhitespace(char) * @see Integer#decode(java.lang.String) */ protected char getDecimalSeparator() { final String systemSep = System.getProperty(SYS_PROP_DECIMAL_SEP); char c; if ((systemSep == null) || (systemSep.length() == 0)) { c = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.getDefault())).getDecimalFormatSymbols() .getDecimalSeparator(); } else { try { c = (char)Integer.decode(systemSep).intValue(); } catch (final NumberFormatException e) { // not encoded according to ยง3.10.1 Java language sepc if (systemSep.startsWith("\\u") || systemSep.startsWith("\\U")) { // NOI18N try { // only hex is accepted when using this notation c = (char)Integer.parseInt(systemSep.substring(2), 16); } catch (final NumberFormatException ex) { LOG.warn("unrecognized separator format '" + systemSep + "', using locale default", ex); // NOI18N c = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.getDefault())) .getDecimalFormatSymbols().getDecimalSeparator(); } } else { c = systemSep.charAt(0); } } if (Character.isWhitespace(c)) { LOG.warn("white space chars not accepted as decimal separator, using locale default"); // NOI18N c = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.getDefault())).getDecimalFormatSymbols() .getDecimalSeparator(); } } return c; } /** * Creates a decimal format that uses the decimal separator of returned from {@link #getDecimalSeparator()} and * disabled grouping. * * @return the decimal format used by this converter */ protected DecimalFormat getDecimalFormat() { final DecimalFormat format = (DecimalFormat)NumberFormat.getNumberInstance(Locale.getDefault()); final DecimalFormatSymbols symbols = format.getDecimalFormatSymbols(); final char decimalSep = getDecimalSeparator(); symbols.setDecimalSeparator(decimalSep); symbols.setGroupingSeparator((char)0); format.setDecimalFormatSymbols(symbols); format.setGroupingUsed(false); return format; } /** * Get the token regex that is used to split the data to convert. This regex never contains the <code>char</code> * returned from {@link #getDecimalSeparator()}. * * @return the token regex that is used to split the data to convert */ protected String getTokenRegex() { final StringBuilder sb = new StringBuilder("["); // NOI18N for (final char sep : getTokenSeparators()) { sb.append(sep); } sb.append("]+"); // NOI18N return sb.toString(); } /** * Gets the separators that are used to build the token regex. This array contains all the separators of * {@link #DEFAULT_TOKEN_SEPARATORS} minus the {@link #getDecimalSeparator()} if it is one of the default * separators. * * @return all token separators used to build the token regex * * @see #getTokenRegex() */ protected char[] getTokenSeparators() { final char decimalSep = getDecimalSeparator(); final char[] chars = new char[DEFAULT_TOKEN_SEPARATORS.length]; int i = 0; int j = 0; for (; i < DEFAULT_TOKEN_SEPARATORS.length; ++i) { if (decimalSep != DEFAULT_TOKEN_SEPARATORS[i]) { chars[j++] = DEFAULT_TOKEN_SEPARATORS[i]; } } if (i == j) { return chars; } else { final char[] ret = new char[j]; System.arraycopy(chars, 0, ret, 0, j); return ret; } } /** * Used to build a human-readable listing of the allowed token separators. * * @return a human-readable listing of the allowed token separators */ private String getFormatSeparators() { final StringBuilder sb = new StringBuilder(); for (final char c : getTokenSeparators()) { if (!Character.isWhitespace(c)) { sb.append('\''); sb.append(c); sb.append('\''); sb.append(", "); // NOI18N } } if (sb.length() > 0) { sb.delete(sb.length() - 2, sb.length()); } int indexOfComma = sb.lastIndexOf(","); // NOI18N if (indexOfComma > 0) { // is the last comma a separator if ((sb.charAt(indexOfComma - 1) == '\'') && (sb.charAt(indexOfComma + 1) == '\'')) { indexOfComma = sb.substring(0, indexOfComma).lastIndexOf(","); // NOI18N } if (indexOfComma > 0) { sb.replace( indexOfComma, indexOfComma + 1, NbBundle.getMessage( AbstractGeometryFromTextConverter.class, "AbstractGeometryFromTextConverter.getFormatSeparators().or")); // NOI18N } } return sb.toString(); } @Override public String getFormatDescription() { return NbBundle.getMessage( AbstractGeometryFromTextConverter.class, "AbstractGeometryFromTextConverter.getFormatDescription().returnValue", // NOI18N getFormatSeparators(), getDecimalSeparator()); } @Override public String getFormatHtmlDescription() { return NbBundle.getMessage( AbstractGeometryFromTextConverter.class, "AbstractGeometryFromTextConverter.getFormatHtmlDescription().returnValue", // NOI18N getFormatSeparators(), getDecimalSeparator()); } }