0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation;
0009: * version 2.1 of the License.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: package org.geotools.data.mif;
0017:
0018: import com.vividsolutions.jts.geom.Coordinate;
0019: import com.vividsolutions.jts.geom.Geometry;
0020: import com.vividsolutions.jts.geom.GeometryFactory;
0021: import com.vividsolutions.jts.geom.LineString;
0022: import com.vividsolutions.jts.geom.LinearRing;
0023: import com.vividsolutions.jts.geom.MultiLineString;
0024: import com.vividsolutions.jts.geom.MultiPolygon;
0025: import com.vividsolutions.jts.geom.Point;
0026: import com.vividsolutions.jts.geom.Polygon;
0027: import com.vividsolutions.jts.geom.PrecisionModel;
0028: import com.vividsolutions.jts.geom.TopologyException;
0029: import com.vividsolutions.jts.io.ParseException;
0030: import org.geotools.data.FeatureReader;
0031: import org.geotools.data.FeatureWriter;
0032: import org.geotools.feature.AttributeType;
0033: import org.geotools.feature.AttributeTypeFactory;
0034: import org.geotools.feature.AttributeTypes;
0035: import org.geotools.feature.Feature;
0036: import org.geotools.feature.FeatureType;
0037: import org.geotools.feature.FeatureTypeBuilder;
0038: import org.geotools.feature.FeatureTypes;
0039: import org.geotools.feature.SchemaException;
0040: import java.io.BufferedReader;
0041: import java.io.File;
0042: import java.io.FileInputStream;
0043: import java.io.FileNotFoundException;
0044: import java.io.FileOutputStream;
0045: import java.io.FileReader;
0046: import java.io.IOException;
0047: import java.io.PrintStream;
0048: import java.net.URI;
0049: import java.nio.channels.FileChannel;
0050: import java.text.SimpleDateFormat;
0051: import java.util.Date; // TODO use java.sql.Date?
0052: import java.util.HashMap;
0053: import java.util.Map;
0054: import java.util.NoSuchElementException;
0055: import java.util.Vector;
0056: import java.util.logging.Logger;
0057:
0058: /**
0059: * <p>
0060: * MIFFile class allows sequential reading and writing of Features in MapInfo
0061: * MIF/MID text file format with a FeatureReader and FeatureWriter.
0062: * </p>
0063: *
0064: * <p>
0065: * This class has been developed starting from MapInfoDataSource.
0066: * </p>
0067: *
0068: * <p>
0069: * Open issues:
0070: * </p>
0071: *
0072: * <ul>
0073: * <li>
0074: * CoordSys clause parsing is still not supported
0075: * </li>
0076: * </ul>
0077: *
0078: *
0079: * @author Luca S. Percich, AMA-MI
0080: * @author Paolo Rizzi, AMA-MI
0081: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/mif/src/main/java/org/geotools/data/mif/MIFFile.java $
0082: * @version $Id: MIFFile.java 27862 2007-11-12 19:51:19Z desruisseaux $
0083: */
0084: public class MIFFile {
0085: // Geometry type identifier constants
0086: private static final String TYPE_NONE = "none";
0087: private static final String TYPE_POINT = "point";
0088: private static final String TYPE_LINE = "line";
0089: private static final String TYPE_PLINE = "pline";
0090: private static final String TYPE_REGION = "region";
0091: private static final String TYPE_TEXT = "text";
0092:
0093: // The following object types are still not supported
0094: private static final String TYPE_ARC = "arc";
0095: private static final String TYPE_RECT = "rect"; // could be converted to polygon
0096: private static final String TYPE_ROUNDRECT = "roundrect";
0097: private static final String TYPE_ELLIPSE = "ellipse";
0098:
0099: // New types introduced after version 6.0, still not supported
0100: private static final String TYPE_MULTIPOINT = "multipoint";
0101: private static final String TYPE_COLLECTION = "collection";
0102:
0103: // String Style Constants
0104: private static final String CLAUSE_SYMBOL = "symbol";
0105: private static final String CLAUSE_PEN = "pen";
0106: private static final String CLAUSE_SMOOTH = "smooth";
0107: private static final String CLAUSE_CENTER = "center";
0108: private static final String CLAUSE_BRUSH = "brush";
0109: private static final String CLAUSE_FONT = "font";
0110: private static final String CLAUSE_ANGLE = "angle";
0111: private static final String CLAUSE_JUSTIFY = "justify";
0112: private static final String CLAUSE_SPACING = "spacing";
0113: private static final String CLAUSE_RIGHT = "right";
0114: private static final String CLAUSE_LABEL = "label";
0115:
0116: // Header parse Constants (& parameter names)
0117: private static final String CLAUSE_COLUMNS = "columns";
0118: public static final int MAX_STRING_LEN = 255; // Max length for MapInfo Char() fields
0119:
0120: // Some (by now useless) default values
0121: private static final String DEFAULT_PEN = "Pen (1,2,0)";
0122: private static final String DEFAULT_BRUSH = "Brush (2,16777215,16777215)";
0123: private static final String DEFAULT_SYMBOL = "Symbol (34,0,12)";
0124: private static Logger LOGGER = org.geotools.util.logging.Logging
0125: .getLogger("org.geotools.data.mif.MIFFile");
0126:
0127: // Header information
0128: private HashMap header = new HashMap();
0129:
0130: // File IO Variables
0131: private File mifFile = null;
0132:
0133: // File IO Variables
0134: private File midFile = null;
0135:
0136: // File IO Variables
0137: private File mifFileOut = null;
0138:
0139: // File IO Variables
0140: private File midFileOut = null;
0141: private Object[] featureDefaults = null;
0142: private char chDelimiter = '\t'; // TAB is the default delimiter if not specified in header
0143:
0144: // Schema variables
0145: private FeatureType featureType = null;
0146: private int numAttribs = 0;
0147: private int geomFieldIndex = -1;
0148: private URI namespace = null;
0149:
0150: // Parameters for coordinate transformation during file i/o
0151: private boolean useTransform = false;
0152: private float multX = 1;
0153: private float multY = 1;
0154: private float sumX = 0;
0155: private float sumY = 0;
0156:
0157: // Options & parameters
0158: private GeometryFactory geomFactory = null;
0159: private Integer SRID = new Integer(0);
0160: private String fieldNameCase;
0161: private String geometryName;
0162: private String geometryClass;
0163: private boolean toGeometryCollection = false;
0164:
0165: /**
0166: * <p>
0167: * This constructor opens an existing MIF/MID file, and creates the
0168: * corresponding schema from the file header
0169: * </p>
0170: *
0171: * <p>
0172: * Allowed parameters in params Map:
0173: * </p>
0174: *
0175: * <ul>
0176: * <li>
0177: * "namespace" = URI of the namespace prefix for FeatureTypes
0178: * </li>
0179: * <li>
0180: * PARAM_GEOMFACTORY = GeometryFactory object to be used for creating
0181: * geometries; alternatively, use PARAM_SRID;
0182: * </li>
0183: * <li>
0184: * PARAM_SRID = SRID to be used for creating geometries;
0185: * </li>
0186: * <li>
0187: * PARAM_FIELDCASE = field names tranformation: "upper" to uppercase |
0188: * "lower" to lowercase | "" none;
0189: * </li>
0190: * <li>
0191: * PARAM_GEOMNAME = <String>, name of the geometry field (defaults to
0192: * "the_geom");
0193: * </li>
0194: * <li>
0195: * PARAM_GEOMTYPE = geometry type handling: "untyped" uses Geometry class |
0196: * "typed" force geometry to the type of the first valid geometry found in
0197: * file | "multi" like typed, but forces LineString to MultilineString and
0198: * Polygon to MultiPolygon; | "Point" | "LineString" | "MultiLineString" |
0199: * "Polygon" | "MultiPolygon" | "Text" forces Geometry to Point and
0200: * creates a MIF_TEXT String field in the schema
0201: * </li>
0202: * </ul>
0203: *
0204: * <p>
0205: * Header clauses values can also be set in the params Map, but they might
0206: * be overridden by values read from MIF header.
0207: * </p>
0208: *
0209: * <p>
0210: * Basic usage:
0211: * </p>
0212: * <pre><code>
0213: * HashMap params = new HashMap();
0214: * // params.put(MIFFile.PARAM_GEOMFACTORY, new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING_SINGLE), SRID));
0215: * params.put(MIFFile.PARAM_SRID, new Integer(SRID));
0216: * params.put(MIFFile.PARAM_FIELDCASE, "upper");
0217: * params.put(MIFFile.PARAM_GEOMNAME, "GEOM");
0218: * params.put(MIFFile.PARAM_GEOMTYPE, "typed");
0219: * MIFFile mf = new MIFFile("c:/some_path/file.mif",params);
0220: * FeatureType ft = mf.getSchema();
0221: * FeatureReader fr = mf.getFeatureReader();
0222: * while (fr.hasNext()) {
0223: * Feature in = fr.next();
0224: * doSomethingWithFeature(in);
0225: * }
0226: * fr.close(); // closes file resources
0227: * </code></pre>
0228: *
0229: * @param path Full pathName of the mif file, can be specified without the
0230: * .mif extension
0231: * @param params Parameters map
0232: *
0233: * @throws IOException If the specified mif file could not be opened
0234: */
0235: public MIFFile(String path, Map params) throws IOException {
0236: // TODO use url instead of String
0237: super ();
0238:
0239: parseParams(params);
0240:
0241: initFiles(path, true);
0242:
0243: MIFFileTokenizer mifTokenizer = new MIFFileTokenizer(
0244: new BufferedReader(new FileReader(mifFile)));
0245:
0246: try {
0247: readMifHeader(false, mifTokenizer);
0248: } catch (Exception e) {
0249: throw new IOException("Can't read MIF header: "
0250: + e.toString());
0251: } finally {
0252: try {
0253: mifTokenizer.close();
0254: } catch (Exception e) {
0255: }
0256: }
0257: }
0258:
0259: /**
0260: * <p>
0261: * This constructor creates a a new MIF/MID file given schema and path. If
0262: * a .mif/.mid file pair already exists, it will be overwritten.
0263: * </p>
0264: *
0265: * <p>
0266: * Basic usage:
0267: * </p>
0268: * <pre><code>
0269: * HashMap params = new HashMap();
0270: * params.put(MIFFile.MIFDataStore.HCLAUSE_COORDSYS, "Nonearth \"m\"");
0271: *
0272: * MIFFile mf = new MIFFile("c:/some_path/", ft, params);
0273: *
0274: *
0275: * FeatureWriter fw = mf.getFeatureWriter();
0276: *
0277: * while(...) {
0278: * Feature f = fw.next();
0279: * f.setAttribute(...,...);
0280: * fw.write();
0281: * }
0282: *
0283: * fw.close();
0284: * </code></pre>
0285: *
0286: * @param path Full path & file name of the MIF file to create, can be
0287: * specified without the .mif extension
0288: * @param featureType
0289: * @param params Parameter map
0290: *
0291: * @throws IOException Couldn't open the specified mif file for writing
0292: * header
0293: * @throws SchemaException Error setting the given FeatureType as the MIF
0294: * schema
0295: */
0296: public MIFFile(String path, FeatureType featureType, HashMap params)
0297: throws IOException, SchemaException {
0298: // TODO use url instead of String
0299: super ();
0300:
0301: parseParams(params);
0302:
0303: setSchema(featureType);
0304: initFiles(path, false);
0305:
0306: PrintStream outMif = new PrintStream(new FileOutputStream(
0307: mifFile, false));
0308: PrintStream outMid = new PrintStream(new FileOutputStream(
0309: midFile, false));
0310:
0311: // writes out header
0312: outMif.println(exportHeader());
0313:
0314: outMif.close();
0315: outMid.close();
0316: }
0317:
0318: /**
0319: * Parses the parameters map into fields:
0320: *
0321: * @param params
0322: *
0323: * @throws IOException Error getting parameters from the specified map
0324: */
0325: private void parseParams(Map params) throws IOException {
0326: if (params == null) {
0327: params = new HashMap();
0328: }
0329:
0330: // Sets defaults for header
0331: setHeaderClause(MIFDataStore.HCLAUSE_VERSION,
0332: (String) getParam(MIFDataStore.HCLAUSE_VERSION, "300",
0333: false, params));
0334: setHeaderClause(MIFDataStore.HCLAUSE_CHARSET,
0335: (String) getParam(MIFDataStore.HCLAUSE_CHARSET,
0336: "WindowsLatin1", false, params));
0337: setHeaderClause(MIFDataStore.HCLAUSE_DELIMITER,
0338: (String) getParam(MIFDataStore.HCLAUSE_DELIMITER,
0339: String.valueOf(chDelimiter), false, params));
0340: chDelimiter = getHeaderClause(MIFDataStore.HCLAUSE_DELIMITER)
0341: .charAt(0);
0342:
0343: setHeaderClause(MIFDataStore.HCLAUSE_UNIQUE, (String) getParam(
0344: MIFDataStore.HCLAUSE_UNIQUE, "", false, params));
0345: setHeaderClause(MIFDataStore.HCLAUSE_INDEX, (String) getParam(
0346: MIFDataStore.HCLAUSE_INDEX, "", false, params));
0347: setHeaderClause(MIFDataStore.HCLAUSE_COORDSYS,
0348: (String) getParam(MIFDataStore.HCLAUSE_COORDSYS, "",
0349: false, params));
0350: setHeaderClause(MIFDataStore.HCLAUSE_TRANSFORM,
0351: (String) getParam(MIFDataStore.HCLAUSE_TRANSFORM, "",
0352: false, params));
0353:
0354: SRID = (Integer) getParam(MIFDataStore.PARAM_SRID, new Integer(
0355: 0), false, params);
0356:
0357: geomFactory = (GeometryFactory) getParam(
0358: MIFDataStore.PARAM_GEOMFACTORY, null, false, params);
0359:
0360: if (geomFactory == null) {
0361: geomFactory = new GeometryFactory(new PrecisionModel(
0362: PrecisionModel.FLOATING), SRID.intValue());
0363: }
0364:
0365: geometryName = (String) getParam(MIFDataStore.PARAM_GEOMNAME,
0366: "the_geom", false, params);
0367: fieldNameCase = ((String) getParam(
0368: MIFDataStore.PARAM_FIELDCASE, "", false, params))
0369: .toLowerCase();
0370:
0371: geometryClass = ((String) getParam(MIFDataStore.PARAM_GEOMTYPE,
0372: "untyped", false, params)).toLowerCase();
0373:
0374: namespace = (URI) getParam("namespace",
0375: FeatureTypes.DEFAULT_NAMESPACE, false, params);
0376: }
0377:
0378: /**
0379: * Returns a parameter value from the parameters map
0380: *
0381: * @param name
0382: * @param defa
0383: * @param required
0384: * @param params
0385: *
0386: *
0387: * @throws IOException if required parameter is missing
0388: */
0389: private Object getParam(String name, Object defa, boolean required,
0390: Map params) throws IOException {
0391: Object result;
0392:
0393: try {
0394: result = params.get(name);
0395: } catch (Exception e) {
0396: result = null;
0397: }
0398:
0399: if (result == null) {
0400: if (required) {
0401: throw new IOException("MIFFile: parameter " + name
0402: + " is required");
0403: }
0404:
0405: result = defa;
0406: }
0407:
0408: return result;
0409: }
0410:
0411: /**
0412: * <p>
0413: * Sets the value for a Header Clause. Possible values are:
0414: * </p>
0415: *
0416: * <ul>
0417: * <li>
0418: * MIFDataStore.HCLAUSE_VERSION = Version number ("310")
0419: * </li>
0420: * <li>
0421: * MIFDataStore.HCLAUSE_CHARSET = Charset name ("WindowsLatin1")
0422: * </li>
0423: * <li>
0424: * MIFDataStore.HCLAUSE_UNIQUE = Comma-separated list of field indexes
0425: * (1..numFields) corresponding to unique values (i.e. street names for
0426: * street segments)
0427: * </li>
0428: * <li>
0429: * MIFDataStore.HCLAUSE_INDEX = Comma-separated list of field indexes
0430: * (1..numFields) indicating which fields have to be indexed in MapInfo
0431: * </li>
0432: * <li>
0433: * MIFDataStore.HCLAUSE_COORDSYS = MapInfo CoordSys clause
0434: * </li>
0435: * <li>
0436: * MIFDataStore.HCLAUSE_TRANSFORM = Comma-separated list of four
0437: * transformation parameters ("1000, 1000, 0, 0")
0438: * </li>
0439: * </ul>
0440: *
0441: *
0442: * @param clause Name of the Header Clause
0443: * @param value Value for the Header Clause
0444: *
0445: * @throws IOException Bad delimiter was specified
0446: */
0447: private void setHeaderClause(String clause, String value)
0448: throws IOException {
0449: if (value == null) {
0450: value = "";
0451: }
0452:
0453: if (clause.equals(MIFDataStore.HCLAUSE_DELIMITER)
0454: && (value.equals("") || value.equals("\""))) {
0455: throw new IOException("Bad delimiter specified");
0456: }
0457:
0458: header.put(clause, value);
0459: }
0460:
0461: /**
0462: * Gets the value for an header clause
0463: *
0464: * @param clause
0465: *
0466: */
0467: public String getHeaderClause(String clause) {
0468: try {
0469: return (String) getParam(clause, "", false, header);
0470: } catch (Exception e) {
0471: return "";
0472: }
0473: }
0474:
0475: /**
0476: * <p>
0477: * Opens the MIF file for input and returns a FeatureReader for accessing
0478: * the features.
0479: * </p>
0480: *
0481: * <p>
0482: * TODO Concurrent file access is still not handled. MUST LOCK FILE and
0483: * return an error if another FeatureReader is open - Handle concurrent
0484: * access with synchronized(mif) / or Filesystem locking is enough?
0485: * </p>
0486: *
0487: * @return A FeatureReader for reading features from MIF/MID file
0488: *
0489: * @throws IOException
0490: */
0491: public FeatureReader getFeatureReader() throws IOException {
0492: MIFFileTokenizer mifTokenizer = null;
0493: MIFFileTokenizer midTokenizer = null;
0494:
0495: // if exists outMIF throw new IOException("File is being accessed in write mode");
0496: try {
0497: mifTokenizer = new MIFFileTokenizer(new BufferedReader(
0498: new FileReader(mifFile)));
0499: midTokenizer = new MIFFileTokenizer(new BufferedReader(
0500: new FileReader(midFile)));
0501: readMifHeader(true, mifTokenizer); // skips header
0502:
0503: return new Reader(mifTokenizer, midTokenizer);
0504: } catch (Exception e) {
0505: if (mifTokenizer != null) {
0506: mifTokenizer.close();
0507: }
0508:
0509: if (midTokenizer != null) {
0510: midTokenizer.close();
0511: }
0512:
0513: throw new IOException("Error initializing reader: "
0514: + e.toString());
0515: }
0516: }
0517:
0518: /**
0519: * Returns a FeatureWriter for writing features to the MIF/MID file.
0520: *
0521: * @return A featureWriter for this file
0522: *
0523: * @throws IOException
0524: */
0525: public FeatureWriter getFeatureWriter() throws IOException {
0526: return getFeatureWriter(false);
0527: }
0528:
0529: /**
0530: * <p>
0531: * Private FeatureWriter in append mode, could be called by
0532: * DataStore.getFeatureWriterAppend(); not implemented yet
0533: * </p>
0534: *
0535: * @param append
0536: *
0537: *
0538: * @throws IOException
0539: */
0540: private FeatureWriter getFeatureWriter(boolean append)
0541: throws IOException {
0542: if (append) {
0543: // copy inMif to OutMIf
0544: } else {
0545: // WriteHeader
0546: }
0547:
0548: PrintStream outMif = new PrintStream(new FileOutputStream(
0549: mifFileOut, append));
0550: PrintStream outMid = new PrintStream(new FileOutputStream(
0551: midFileOut, append));
0552:
0553: return new Writer(outMif, outMid, append);
0554: }
0555:
0556: /**
0557: * Creates the MIF file header
0558: *
0559: * @return the Header as a String
0560: *
0561: * @throws SchemaException A required header clause is missing.
0562: */
0563: private String exportHeader() throws SchemaException {
0564: // Header tags passed in parameters are overridden by the tags read from mif file
0565: String header = exportClause(MIFDataStore.HCLAUSE_VERSION,
0566: true, false)
0567: + exportClause(MIFDataStore.HCLAUSE_CHARSET, true, true) // TODO Charset clause support should imply character conversion????
0568: + exportClause(MIFDataStore.HCLAUSE_DELIMITER, true,
0569: true)
0570: + exportClause(MIFDataStore.HCLAUSE_UNIQUE, false,
0571: false)
0572: + exportClause(MIFDataStore.HCLAUSE_INDEX, false, false)
0573: + exportClause(MIFDataStore.HCLAUSE_COORDSYS, false,
0574: false)
0575: + exportClause(MIFDataStore.HCLAUSE_TRANSFORM, false,
0576: false);
0577:
0578: header += ("Columns " + (numAttribs - 1) + "\n");
0579:
0580: for (int i = 1; i < numAttribs; i++) {
0581: AttributeType at = featureType.getAttributeType(i);
0582: header += (" " + at.getName() + " "
0583: + getMapInfoAttrType(at) + "\n");
0584: }
0585:
0586: header += "Data\n";
0587:
0588: return header;
0589: }
0590:
0591: private String exportClause(String clause, boolean required,
0592: boolean quote) throws SchemaException {
0593: String result = getHeaderClause(clause);
0594:
0595: if (!result.equals("")) {
0596: if (quote) {
0597: result = MIFStringTokenizer.strQuote(result);
0598: }
0599:
0600: return clause + " " + result + "\n";
0601: }
0602:
0603: if (required) {
0604: throw new SchemaException("Header clause " + clause
0605: + " is required.");
0606: }
0607:
0608: return "";
0609: }
0610:
0611: /**
0612: * Maps an AttributeType to a MapInfo field type
0613: *
0614: * @param at Attribute Type
0615: *
0616: * @return the String description of the MapInfo Type
0617: */
0618: private String getMapInfoAttrType(AttributeType at) {
0619: if (at.getType() == String.class) {
0620: int l = AttributeTypes.getFieldLength(at, MAX_STRING_LEN);
0621:
0622: if (l <= 0) {
0623: l = MAX_STRING_LEN;
0624: }
0625:
0626: return "Char(" + l + ")";
0627: } else if (at.getType() == Integer.class) {
0628: return "Integer";
0629: } else if ((at.getType() == Double.class)
0630: || (at.getType() == Float.class)) {
0631: return "Float";
0632: } else if (at.getType() == Boolean.class) {
0633: return "Logical";
0634: } else if (at.getType() == Date.class) {
0635: return "Date";
0636: } else {
0637: return "Char(" + MAX_STRING_LEN + ")"; // TODO Should it raise an exception here (UnsupportedSchema) ?
0638: }
0639: }
0640:
0641: /**
0642: * Sets the path name of the MIF and MID files
0643: *
0644: * @param path The full path of the .mif file, with or without extension
0645: * @param mustExist True if opening file for reading
0646: *
0647: * @throws FileNotFoundException
0648: */
0649: private void initFiles(String path, boolean mustExist)
0650: throws FileNotFoundException {
0651: File file = new File(path);
0652:
0653: if (file.isDirectory()) {
0654: throw new FileNotFoundException(path + " is a directory");
0655: }
0656:
0657: String fName = getMifName(file.getName());
0658: file = file.getParentFile();
0659:
0660: mifFile = getFileHandler(file, fName, ".mif", mustExist);
0661: midFile = getFileHandler(file, fName, ".mid", mustExist);
0662:
0663: mifFileOut = getFileHandler(file, fName, ".mif.out", false);
0664: midFileOut = getFileHandler(file, fName, ".mid.out", false);
0665: }
0666:
0667: /**
0668: * Returns the name of a .mif file without extension
0669: *
0670: * @param fName The file name, possibly with .mif extension
0671: *
0672: * @return The name with no extension
0673: *
0674: * @throws FileNotFoundException if extension was other than "mif"
0675: */
0676: protected static String getMifName(String fName)
0677: throws FileNotFoundException {
0678: int ext = fName.lastIndexOf(".");
0679:
0680: if (ext > 0) {
0681: String theExt = fName.substring(ext + 1).toLowerCase();
0682:
0683: if (!(theExt.equals("mif"))) {
0684: throw new FileNotFoundException(
0685: "Please specify a .mif file extension.");
0686: }
0687:
0688: fName = fName.substring(0, ext);
0689: }
0690:
0691: return fName;
0692: }
0693:
0694: /**
0695: * Utility function for initFiles - returns a File given a parent path, the
0696: * file name without extension and the extension Tests different extension
0697: * case for case-sensitive filesystems
0698: *
0699: * @param path Directory containing the file
0700: * @param fileName Name of the file with no extension
0701: * @param ext extension with trailing "."
0702: * @param mustExist If true, raises an excaption if the file does not exist
0703: *
0704: * @return The File object
0705: *
0706: * @throws FileNotFoundException
0707: */
0708: protected static File getFileHandler(File path, String fileName,
0709: String ext, boolean mustExist) throws FileNotFoundException {
0710: File file = new File(path, fileName + ext);
0711:
0712: if (file.exists() || !mustExist) {
0713: return file;
0714: }
0715:
0716: file = new File(path, fileName + ext.toUpperCase());
0717:
0718: if (file.exists()) {
0719: return file;
0720: }
0721:
0722: throw new FileNotFoundException("Can't find file: "
0723: + file.getName());
0724: }
0725:
0726: /**
0727: * Reads the header from the given MIF file stream tokenizer
0728: *
0729: * @param skipRead Skip the header, just to get to the data section
0730: * @param mif
0731: *
0732: * @throws IOException
0733: * @throws SchemaException Error reading header information
0734: */
0735: private void readMifHeader(boolean skipRead, MIFFileTokenizer mif)
0736: throws IOException, SchemaException {
0737: try {
0738: String tok;
0739: boolean hasMifText = false;
0740: AttributeType[] columns = null;
0741:
0742: while (mif.readLine()) {
0743: tok = mif.getToken().toLowerCase();
0744:
0745: // "data" might be a field name, in this case the type name would follow on the same line
0746: if (tok.equals("data") && mif.getLine().equals("")) {
0747: break;
0748: }
0749:
0750: if (skipRead) {
0751: continue;
0752: }
0753:
0754: if (tok.equals(MIFDataStore.HCLAUSE_VERSION)) {
0755: setHeaderClause(MIFDataStore.HCLAUSE_VERSION, mif
0756: .getLine());
0757:
0758: continue;
0759: }
0760:
0761: if (tok.equals(MIFDataStore.HCLAUSE_CHARSET)) {
0762: setHeaderClause(MIFDataStore.HCLAUSE_CHARSET, mif
0763: .getToken(' ', false, true));
0764:
0765: continue;
0766: }
0767:
0768: if (tok.equals(MIFDataStore.HCLAUSE_DELIMITER)) {
0769: setHeaderClause(MIFDataStore.HCLAUSE_DELIMITER, mif
0770: .getToken(' ', false, true));
0771: chDelimiter = getHeaderClause(
0772: MIFDataStore.HCLAUSE_DELIMITER).charAt(0);
0773:
0774: continue;
0775: }
0776:
0777: if (tok.equals(MIFDataStore.HCLAUSE_UNIQUE)) {
0778: setHeaderClause(MIFDataStore.HCLAUSE_UNIQUE, mif
0779: .getLine());
0780:
0781: continue;
0782: }
0783:
0784: if (tok.equals(MIFDataStore.HCLAUSE_COORDSYS)) {
0785: setHeaderClause(MIFDataStore.HCLAUSE_COORDSYS, mif
0786: .getLine());
0787:
0788: continue;
0789: }
0790:
0791: if (tok.equals(MIFDataStore.HCLAUSE_INDEX)) {
0792: setHeaderClause(MIFDataStore.HCLAUSE_INDEX, mif
0793: .getLine());
0794:
0795: continue;
0796: }
0797:
0798: if (tok.equals(MIFDataStore.HCLAUSE_TRANSFORM)) {
0799: useTransform = true;
0800: multX = Float.parseFloat("0" + mif.getToken(','));
0801: multY = Float.parseFloat("0" + mif.getToken(','));
0802: sumX = Float.parseFloat("0" + mif.getToken(','));
0803: sumY = Float.parseFloat("0" + mif.getToken(','));
0804:
0805: if (multX == 0) {
0806: multX = 1;
0807: }
0808:
0809: if (multY == 0) {
0810: multY = 1;
0811: }
0812:
0813: continue;
0814: }
0815:
0816: if (tok.equals(CLAUSE_COLUMNS)) {
0817: int cols;
0818:
0819: try {
0820: cols = Integer.parseInt(mif.getLine());
0821: } catch (NumberFormatException nfexp) {
0822: throw new IOException("bad number of colums: "
0823: + mif.getLine());
0824: }
0825:
0826: // Columns <n> does not take into account the geometry column, so we increment
0827: columns = new AttributeType[++cols];
0828:
0829: String name;
0830: String type;
0831: Object defa;
0832: Class typeClass;
0833: int size;
0834:
0835: for (int i = 1; i < cols; i++) {
0836: if (!mif.readLine()) {
0837: throw new IOException(
0838: "Expected column definition");
0839: }
0840:
0841: name = mif.getToken();
0842:
0843: if (fieldNameCase.equalsIgnoreCase("upper")) {
0844: name = name.toUpperCase();
0845: } else if (fieldNameCase
0846: .equalsIgnoreCase("lower")) {
0847: name = name.toLowerCase();
0848: }
0849:
0850: type = mif.getToken('(').toLowerCase();
0851: defa = null;
0852: typeClass = null;
0853: size = 4;
0854:
0855: if (type.equals("float")
0856: || type.equals("decimal")) {
0857: typeClass = Double.class;
0858: size = 8;
0859: defa = new Double(0.0);
0860:
0861: // TODO: check precision?
0862: } else if (type.startsWith("char")) {
0863: typeClass = String.class;
0864: size = Integer.parseInt(mif.getToken(')'));
0865: defa = "";
0866: } else if (type.equals("integer")
0867: || type.equals("smallint")) {
0868: typeClass = Integer.class;
0869: defa = new Integer(0);
0870:
0871: // TODO: apply a restriction for Smallint (value between -32768 and +32767)
0872: } else if (type.equals("logical")) {
0873: typeClass = Boolean.class;
0874: size = 2; // ???
0875: defa = new Boolean(false);
0876: } else if (type.equals("date")) {
0877: typeClass = Date.class; // MapInfo format: yyyymmdd
0878: size = 4; // ???
0879: defa = null; // Dates are "nillable" (like Strings can be empty)
0880: } else {
0881: LOGGER.fine("unknown type in mif/mid read "
0882: + type + " storing as String");
0883: typeClass = String.class;
0884: size = 254;
0885: defa = "";
0886: }
0887:
0888: // Apart from Geometry, MapInfo table fields cannot be null, so Nillable is always false and default value must always be provided!
0889: columns[i] = AttributeTypeFactory
0890: .newAttributeType(name, typeClass,
0891: (defa == null), size, defa);
0892: }
0893: }
0894: }
0895:
0896: // Builds schema if not in skip mode...
0897: if (!skipRead) {
0898: Class geomType = null;
0899:
0900: String geomClass = geometryClass.toLowerCase();
0901:
0902: if (geomClass.equals("untyped")) {
0903: geomType = Geometry.class;
0904: } else if (geomClass.equals("typed")) {
0905: toGeometryCollection = false;
0906: } else if (geomClass.equals("multi")) {
0907: toGeometryCollection = true;
0908: } else if (geomClass.equals("point")) {
0909: geomType = Point.class;
0910: } else if (geomClass.equals("text")) {
0911: geomType = Point.class;
0912: hasMifText = true;
0913: } else if (geomClass.equals("linestring")) {
0914: geomType = LineString.class;
0915: } else if (geomClass.equals("multilinestring")) {
0916: geomType = MultiLineString.class;
0917: toGeometryCollection = true;
0918: } else if (geomClass.equals("polygon")) {
0919: geomType = Polygon.class;
0920: } else if (geomClass.equals("multipolygon")) {
0921: geomType = MultiPolygon.class;
0922: toGeometryCollection = true;
0923: } else {
0924: throw new SchemaException(
0925: "Bad geometry type option: " + geomClass);
0926: }
0927:
0928: // Determine geometry type from the first non-null geometry read from mif file
0929: if (geomType == null) {
0930: Reader reader = new Reader(mif, null);
0931: Geometry geom = null;
0932:
0933: while (!reader.mifEOF) {
0934: geom = reader.readGeometry();
0935: hasMifText = (!reader.mifText.equals(""));
0936:
0937: if (geom != null) {
0938: geomType = geom.getClass();
0939:
0940: if (toGeometryCollection) {
0941: if (geomType
0942: .isAssignableFrom(Polygon.class)) {
0943: geomType = MultiPolygon.class;
0944: } else if (geomType
0945: .isAssignableFrom(LineString.class)) {
0946: geomType = MultiLineString.class;
0947: }
0948: }
0949:
0950: break;
0951: }
0952: }
0953:
0954: reader.close();
0955: reader = null;
0956: }
0957:
0958: if (geomType == null) {
0959: throw new SchemaException(
0960: "Unable to determine geometry type from mif file");
0961: }
0962:
0963: columns[0] = AttributeTypeFactory.newAttributeType(
0964: geometryName, geomType, true);
0965:
0966: try {
0967: String typeName = mifFile.getName();
0968: typeName = typeName.substring(0, typeName
0969: .indexOf("."));
0970:
0971: FeatureTypeBuilder builder = FeatureTypeBuilder
0972: .newInstance(typeName);
0973:
0974: builder.setNamespace(namespace);
0975:
0976: for (int i = 0; i < columns.length; i++)
0977: builder.addType(columns[i]);
0978:
0979: if (hasMifText) {
0980: builder.addType(AttributeTypeFactory
0981: .newAttributeType("MIF_TEXT",
0982: String.class, true));
0983: }
0984:
0985: setSchema(builder.getFeatureType());
0986: } catch (SchemaException schexp) {
0987: throw new SchemaException(
0988: "Exception creating feature type from MIF header: "
0989: + schexp.toString());
0990: }
0991: }
0992: } catch (Exception e) {
0993: throw new IOException(
0994: "IOException reading MIF header, line "
0995: + mif.getLineNumber() + ": "
0996: + e.getMessage());
0997: }
0998: }
0999:
1000: /**
1001: * Returns the MIF schema
1002: *
1003: * @return the current FeatureType associated with the MIF file
1004: */
1005: public FeatureType getSchema() {
1006: return featureType;
1007: }
1008:
1009: /**
1010: * Sets the schema (FeatureType) and creates value setters and IO object
1011: * buffer
1012: *
1013: * @param ft
1014: *
1015: * @throws SchemaException The given FeatureType is not compatible with
1016: * MapInfo format
1017: */
1018: private void setSchema(FeatureType ft) throws SchemaException {
1019: featureType = ft;
1020:
1021: numAttribs = featureType.getAttributeCount();
1022: geomFieldIndex = -1;
1023:
1024: // Creates the input buffer for reading MID file
1025: featureDefaults = new Object[numAttribs];
1026:
1027: for (int i = 0; i < featureType.getAttributeCount(); i++) {
1028: AttributeType at = featureType.getAttributeType(i);
1029:
1030: Class atc = at.getType();
1031:
1032: if (Geometry.class.isAssignableFrom(atc)) {
1033: if (geomFieldIndex >= 0) {
1034: throw new SchemaException(
1035: "Feature Types with more than one geometric attribute are not supported.");
1036: }
1037:
1038: if (i > 0) {
1039: throw new SchemaException(
1040: "Geometry must be the first attribute in schema.");
1041: }
1042:
1043: geomFieldIndex = i; // = 0
1044: }
1045: }
1046:
1047: MIFValueSetter[] tmp = getValueSetters();
1048:
1049: for (int i = 0; i < featureType.getAttributeCount(); i++) {
1050: if (i != geomFieldIndex) {
1051: tmp[i].setString("");
1052: featureDefaults[i] = tmp[i].getValue();
1053: }
1054: }
1055: }
1056:
1057: /**
1058: * Gets the ValueSetters
1059: *
1060: * @return An array of valueSetters to be used for IO operations
1061: *
1062: * @throws SchemaException An attribute of an unsupported type was found.
1063: */
1064: private MIFValueSetter[] getValueSetters() throws SchemaException {
1065: MIFValueSetter[] fieldValueSetters = new MIFValueSetter[numAttribs];
1066:
1067: for (int i = 0; i < featureType.getAttributeCount(); i++) {
1068: AttributeType at = featureType.getAttributeType(i);
1069: Class atc = at.getType();
1070:
1071: if (i == geomFieldIndex) {
1072: fieldValueSetters[i] = null;
1073: } else if (atc == Integer.class) {
1074: fieldValueSetters[i] = new MIFValueSetter("0") {
1075: protected void stringToValue() throws Exception {
1076: objValue = new Integer(strValue);
1077: }
1078: };
1079: } else if (atc == Double.class) {
1080: fieldValueSetters[i] = new MIFValueSetter("0") {
1081: protected void stringToValue() throws Exception {
1082: objValue = new Double(strValue);
1083: }
1084:
1085: protected void valueToString() {
1086: // TODO use DecimalFormat class!!!
1087: super .valueToString();
1088: }
1089: };
1090: } else if (atc == Float.class) {
1091: fieldValueSetters[i] = new MIFValueSetter("0") {
1092: protected void stringToValue() throws Exception {
1093: objValue = new Float(strValue);
1094: }
1095:
1096: protected void valueToString() {
1097: // TODO use DecimalFormat class!!!
1098: super .valueToString();
1099: }
1100: };
1101: } else if (atc == Boolean.class) {
1102: fieldValueSetters[i] = new MIFValueSetter("false") {
1103: protected void stringToValue() throws Exception {
1104: objValue = new Boolean(
1105: "T".equalsIgnoreCase(strValue) ? "true"
1106: : ("F"
1107: .equalsIgnoreCase(strValue) ? "false"
1108: : strValue));
1109: }
1110:
1111: protected void valueToString() {
1112: if ((objValue == null)
1113: || (((Boolean) objValue).booleanValue() == false)) {
1114: strValue = "F";
1115: } else {
1116: strValue = "T";
1117: }
1118: }
1119: };
1120: } else if (Date.class.isAssignableFrom(atc)) {
1121: // TODO Check conversion of date values - switch to java.sql.Date
1122: fieldValueSetters[i] = new MIFValueSetter("") {
1123: protected SimpleDateFormat dateFormat = new SimpleDateFormat(
1124: "yyyyMMdd");
1125:
1126: protected void stringToValue() throws Exception {
1127: if ((strValue != null) && !strValue.equals("")) {
1128: objValue = dateFormat.parse(strValue);
1129: } else {
1130: objValue = null;
1131: }
1132:
1133: // Date.valueOf(strValue.substring(0, 4) + "-" + strValue.substring(4, 6) + "-" + strValue.substring(6));
1134: }
1135:
1136: protected void valueToString() {
1137: if (objValue == null) {
1138: strValue = "";
1139: } else {
1140: strValue = dateFormat.format(objValue);
1141:
1142: // strValue = ((Date) objValue).getYear() + "" + ((Date) objValue).getMonth() + "" + ((Date) objValue).getDay();
1143: }
1144: }
1145: };
1146: } else if (atc == String.class) {
1147: fieldValueSetters[i] = new MIFValueSetter("") {
1148: protected void stringToValue() throws Exception {
1149: objValue = new String(strValue);
1150: }
1151:
1152: // Quotes the string
1153: protected void valueToString() {
1154: strValue = new String("\""
1155: + objValue.toString().replaceAll("\"",
1156: "\"\"") + "\"");
1157: }
1158: };
1159: } else {
1160: throw new SchemaException(
1161: "Unsupported attribute type: " + atc.getName());
1162: }
1163: }
1164:
1165: return fieldValueSetters;
1166: }
1167:
1168: /**
1169: * Utility function for copying or moving files
1170: *
1171: * @param in Source file
1172: * @param out Destination file
1173: * @param deleteIn If true, source will be deleted upon successfull copy
1174: *
1175: * @throws IOException
1176: */
1177: protected static void copyFileAndDelete(File in, File out,
1178: boolean deleteIn) throws IOException {
1179: try {
1180: FileChannel sourceChannel = new FileInputStream(in)
1181: .getChannel();
1182: FileChannel destinationChannel = new FileOutputStream(out)
1183: .getChannel();
1184: destinationChannel.transferFrom(sourceChannel, 0,
1185: sourceChannel.size());
1186: if (deleteIn) {
1187: in.delete();
1188: }
1189: } catch (Exception e) {
1190: throw new IOException(e.getMessage());
1191: }
1192: }
1193:
1194: /**
1195: * <p>
1196: * Private FeatureReader inner class for reading Features from the MIF file
1197: * </p>
1198: */
1199: private class Reader implements FeatureReader {
1200: private MIFFileTokenizer mif = null;
1201: private MIFFileTokenizer mid = null;
1202: private boolean mifEOF = false;
1203: private String mifText = ""; // caption for text objects
1204: private Feature inputFeature = null;
1205: private Object[] inputBuffer = null;
1206: private MIFValueSetter[] fieldValueSetters;
1207:
1208: private Reader(MIFFileTokenizer mifTokenizer,
1209: MIFFileTokenizer midTokenizer) throws IOException {
1210: inputBuffer = new Object[numAttribs];
1211:
1212: mif = mifTokenizer;
1213: mid = midTokenizer;
1214:
1215: // numAttribs == 0 when Reader is called from within readMifHeader for determining geometry Type
1216: if (numAttribs > 0) {
1217: try {
1218: fieldValueSetters = getValueSetters();
1219: } catch (SchemaException e) {
1220: throw new IOException(e.getMessage());
1221: }
1222:
1223: inputFeature = readFeature();
1224: }
1225: }
1226:
1227: public boolean hasNext() {
1228: return (inputFeature != null);
1229: }
1230:
1231: // Reads the next feature and returns the last one
1232: public Feature next() throws NoSuchElementException {
1233: if (inputFeature == null) {
1234: throw new NoSuchElementException(
1235: "Reached the end of MIF file");
1236: }
1237:
1238: Feature temp = inputFeature;
1239:
1240: try {
1241: inputFeature = readFeature();
1242: } catch (Exception e) {
1243: throw new NoSuchElementException(
1244: "Error retrieving next feature: "
1245: + e.toString());
1246: }
1247:
1248: return temp;
1249: }
1250:
1251: public FeatureType getFeatureType() {
1252: return featureType;
1253: }
1254:
1255: public void close() {
1256: try {
1257: if (mif != null) {
1258: mif.close();
1259: }
1260:
1261: if (mid != null) {
1262: mid.close();
1263: }
1264: } finally {
1265: mif = null;
1266: mid = null;
1267: }
1268: }
1269:
1270: protected void finalize() throws Throwable {
1271: close();
1272: super .finalize();
1273: }
1274:
1275: /**
1276: * Reads a single MIF Object (Point, Line, Region, etc.) as a Feature
1277: *
1278: * @return The feature, or null if the end of file was reached
1279: *
1280: * @throws IOException
1281: */
1282: private Feature readFeature() throws IOException {
1283: Feature feature = null;
1284: Geometry geom = readGeometry();
1285:
1286: if (mifEOF) {
1287: return null;
1288: }
1289:
1290: if (!mid.readLine()) {
1291: // TODO According to MapInfo spec., MID file is optional... in this case we should return the default values for the feature
1292: if (geom != null) {
1293: throw new IOException("Unexpected end of MID file.");
1294: }
1295:
1296: return null;
1297: }
1298:
1299: // Reads data from mid file
1300: // Assumes that geomFieldIndex == 0
1301: try {
1302: String tok = "";
1303: int col = 0;
1304:
1305: while (!mid.isEmpty()) {
1306: tok = mid.getToken(chDelimiter, false, true);
1307:
1308: if (!fieldValueSetters[++col].setString(tok)) {
1309: LOGGER.severe("Bad value:"
1310: + fieldValueSetters[col].getError());
1311: }
1312:
1313: inputBuffer[col] = fieldValueSetters[col]
1314: .getValue();
1315: }
1316:
1317: if (!mifText.equals("")) {
1318: // MIF_TEXT MUST BE the LAST Field for now
1319: inputBuffer[++col] = mifText;
1320:
1321: // a better approach could be using a separate array of value setters for MIF_ fields
1322: // (TEXT, ANGLE...)
1323: }
1324:
1325: if (col != (numAttribs - 1)) {
1326: throw new Exception(
1327: "Bad number of attributes read on MID row "
1328: + mid.getLineNumber() + ": found "
1329: + col + ", expecting " + numAttribs);
1330: }
1331: } catch (Exception e) {
1332: throw new IOException("Error reading MID file, line "
1333: + mid.getLineNumber() + ": " + e.getMessage());
1334: }
1335:
1336: // Now add geometry and build the feature
1337: try {
1338: inputBuffer[0] = geom;
1339: feature = featureType.create(inputBuffer);
1340: } catch (Exception e) {
1341: throw new IOException("Exception building feature: "
1342: + e.getMessage());
1343: }
1344:
1345: return feature;
1346: }
1347:
1348: /**
1349: * Reads one geometric object from the MIF file
1350: *
1351: * @return The geometry object
1352: *
1353: * @throws IOException Error retrieving geometry from input MIF stream
1354: */
1355: private Geometry readGeometry() throws IOException {
1356: mifText = "";
1357:
1358: if (!mif.readLine()) {
1359: mifEOF = true;
1360:
1361: return null;
1362: }
1363:
1364: Geometry geom = null;
1365:
1366: try {
1367: // First of all reads geometry
1368: String objType = mif.getToken().toLowerCase();
1369:
1370: if (objType.equals(TYPE_NONE)) {
1371: geom = null;
1372: } else if (objType.equals(TYPE_POINT)) {
1373: geom = readPointObject();
1374: } else if (objType.equals(TYPE_LINE)) {
1375: geom = readLineObject();
1376: } else if (objType.equals(TYPE_PLINE)) {
1377: geom = readPLineObject();
1378: } else if (objType.equals(TYPE_REGION)) {
1379: geom = readRegionObject();
1380: } else if (objType.equals(TYPE_TEXT)) {
1381: geom = readTextObject();
1382: } else if (objType.equals(CLAUSE_PEN)
1383: || objType.equals(CLAUSE_SYMBOL)
1384: || objType.equals(CLAUSE_SMOOTH)
1385: || objType.equals(CLAUSE_CENTER)
1386: || objType.equals(CLAUSE_BRUSH)
1387: || objType.equals(CLAUSE_FONT)
1388: || objType.equals(CLAUSE_ANGLE)
1389: || objType.equals(CLAUSE_JUSTIFY)
1390: || objType.equals(CLAUSE_SPACING)
1391: || objType.equals(CLAUSE_LABEL)) {
1392: // Symply ignores styling clauses, so let's read the next lines
1393: geom = readGeometry();
1394: } else {
1395: // TODO add MultiPoint & Collection!!!
1396: throw new IOException(
1397: "Unknown or unsupported object in mif file:"
1398: + objType);
1399: }
1400: } catch (Exception e) {
1401: throw new IOException("File " + mifFile.getName()
1402: + ", line " + mif.getLineNumber() + ": "
1403: + e.getMessage());
1404: }
1405:
1406: return geom;
1407: }
1408:
1409: /**
1410: * Reads Multi-Line (PLine) information from the MIF stream
1411: *
1412: * @return The (MULTI)LINESTRING object read
1413: *
1414: * @throws IOException Error retrieving geometry from input MIF stream
1415: */
1416: private Geometry readPLineObject() throws IOException {
1417: try {
1418: String tmp = mif.getToken(' ', true);
1419: int numsections = 1;
1420: int numpoints = 0;
1421:
1422: if (tmp.equalsIgnoreCase("MULTIPLE")) {
1423: numsections = Integer.parseInt(mif.getToken(' ',
1424: true)); //read the number of sections
1425: numpoints = Integer.parseInt(mif
1426: .getToken(' ', true)); //read the number of points
1427: } else {
1428: // already got the number of points, simply parse it
1429: numpoints = Integer.parseInt(tmp);
1430: }
1431:
1432: LineString[] lineStrings = new LineString[numsections];
1433:
1434: // Read each polyline
1435: for (int i = 0; i < lineStrings.length; i++) {
1436: if (numpoints == 0) {
1437: numpoints = Integer.parseInt(mif.getToken(' ',
1438: true));
1439: }
1440:
1441: Coordinate[] coords = new Coordinate[numpoints];
1442:
1443: // Read each point
1444: for (int p = 0; p < coords.length; p++) {
1445: coords[p] = readMIFCoordinate();
1446: }
1447:
1448: numpoints = 0;
1449:
1450: lineStrings[i] = geomFactory
1451: .createLineString(coords);
1452: }
1453:
1454: if ((numsections == 1) && !toGeometryCollection) {
1455: return (Geometry) lineStrings[0];
1456: }
1457:
1458: return (Geometry) geomFactory
1459: .createMultiLineString(lineStrings);
1460: } catch (Exception e) {
1461: throw new IOException(
1462: "Exception reading PLine data from MIF file : "
1463: + e.toString());
1464: }
1465: }
1466:
1467: /**
1468: * Reads Region (Polygon) information from the MIF stream
1469: *
1470: * @return The (MULTI)POLYGON object
1471: *
1472: * @throws IOException Error retrieving geometry from input MIF stream
1473: */
1474: private Geometry readRegionObject() throws IOException {
1475: try {
1476: int numpolygons = Integer.parseInt(mif.getToken(' ',
1477: true));
1478:
1479: Vector polygons = new Vector();
1480:
1481: LinearRing tmpRing = null;
1482: Polygon shell = null;
1483: LinearRing shellRing = null;
1484: Vector holes = null;
1485: boolean savePolygon;
1486:
1487: // Read all linearrings;
1488: for (int i = 0; i < numpolygons; i++) {
1489: // Read coordinates & create ring
1490: int numpoints = Integer.parseInt(mif.getToken(' ',
1491: true));
1492: Coordinate[] coords = new Coordinate[numpoints + 1];
1493:
1494: for (int p = 0; p < numpoints; p++) {
1495: coords[p] = readMIFCoordinate();
1496: }
1497:
1498: coords[coords.length - 1] = coords[0];
1499: tmpRing = geomFactory.createLinearRing(coords);
1500:
1501: /*
1502: * In MIF format a polygon is described as a list of rings, with no info wether
1503: * a ring is a hole or a shell, so we have to determine it by checking if
1504: * a ring in contained in the previously defined shell
1505: */
1506: if ((shell != null) && shell.contains(tmpRing)) {
1507: holes.add(tmpRing);
1508: tmpRing = null; // mark as done
1509: savePolygon = (i == (numpolygons - 1));
1510: } else {
1511: // New polygon, must save previous if it's not the first ring
1512: savePolygon = (i > 0);
1513: }
1514:
1515: if (savePolygon) {
1516: LinearRing[] h = null;
1517:
1518: if (holes.size() > 0) {
1519: h = new LinearRing[holes.size()];
1520:
1521: for (int hole = 0; hole < holes.size(); hole++) {
1522: h[hole] = (LinearRing) holes.get(hole);
1523: }
1524: }
1525:
1526: polygons.add(geomFactory.createPolygon(
1527: shellRing, h));
1528:
1529: shellRing = null;
1530: }
1531:
1532: // Build the polygon needed for testing holes
1533: if (tmpRing != null) {
1534: shellRing = tmpRing;
1535: shell = geomFactory.createPolygon(shellRing,
1536: null);
1537: holes = new Vector();
1538: }
1539: }
1540:
1541: if (shellRing != null) {
1542: polygons.add(geomFactory.createPolygon(shellRing,
1543: null));
1544: }
1545:
1546: try {
1547: if ((polygons.size() == 1) && !toGeometryCollection) {
1548: return (Polygon) polygons.get(0);
1549: }
1550:
1551: Polygon[] polys = new Polygon[polygons.size()];
1552:
1553: for (int i = 0; i < polygons.size(); i++) {
1554: polys[i] = (Polygon) polygons.get(i);
1555: }
1556:
1557: return geomFactory.createMultiPolygon(polys);
1558: } catch (TopologyException topexp) {
1559: throw new TopologyException(
1560: "TopologyException reading Region polygon : "
1561: + topexp.toString());
1562: }
1563: } catch (Exception e) {
1564: throw new IOException(
1565: "Exception reading Region data from MIF file : "
1566: + e.toString());
1567: }
1568: }
1569:
1570: /**
1571: * Reads a couple of coordinates (x,y) from input stream, applying the
1572: * transform factor if required.
1573: *
1574: * @return A Coordinate object, or null if error encountered
1575: *
1576: * @throws IOException if couldn't build a valid Coordinate object
1577: */
1578: private Coordinate readMIFCoordinate() throws IOException {
1579: String x;
1580: String y;
1581:
1582: try {
1583: x = mif.getToken(' ', true);
1584: y = mif.getToken();
1585:
1586: if (x.equals("") || y.equals("")) {
1587: throw new IOException("End of file.");
1588: }
1589:
1590: Coordinate result = new Coordinate(Double
1591: .parseDouble(x), Double.parseDouble(y));
1592:
1593: if (useTransform) {
1594: result.x = (result.x * multX) + sumX;
1595: result.y = (result.y * multY) + sumY;
1596: }
1597:
1598: return result;
1599: } catch (Exception e) {
1600: throw new IOException("Error getting coordinates: "
1601: + e.toString());
1602: }
1603: }
1604:
1605: /**
1606: * Reads Point information from the MIF stream
1607: *
1608: * @return The next POINT object read
1609: *
1610: * @throws IOException Error retrieving geometry from input MIF stream
1611: */
1612: private Geometry readPointObject() throws IOException {
1613: return geomFactory.createPoint(readMIFCoordinate());
1614: }
1615:
1616: /**
1617: * Reads Line information from the MIF stream
1618: *
1619: * @return a LINESTRING object
1620: *
1621: * @throws IOException Error retrieving geometry from input MIF stream
1622: */
1623: private Geometry readLineObject() throws IOException {
1624: Coordinate[] cPoints = new Coordinate[2];
1625: cPoints[0] = readMIFCoordinate();
1626: cPoints[1] = readMIFCoordinate();
1627:
1628: LineString[] result = { geomFactory
1629: .createLineString(cPoints) };
1630:
1631: if (toGeometryCollection) {
1632: return geomFactory.createMultiLineString(result);
1633: }
1634:
1635: return result[0];
1636: }
1637:
1638: private Geometry readTextObject() throws IOException {
1639: try {
1640: mifText = mif.getToken(' ', true, true);
1641: } catch (ParseException e) {
1642: throw new IOException(e.getMessage());
1643: }
1644:
1645: Coordinate c1 = readMIFCoordinate();
1646: Coordinate c2 = readMIFCoordinate();
1647: Coordinate p = new Coordinate((c1.x + c2.x) / 2,
1648: (c1.y + c2.y) / 2);
1649:
1650: return geomFactory.createPoint(p);
1651: }
1652: }
1653:
1654: /**
1655: * <p>
1656: * MIF FeatureWriter
1657: * </p>
1658: */
1659: private class Writer implements FeatureWriter {
1660: private PrintStream outMif = null;
1661: private PrintStream outMid = null;
1662: private FeatureReader innerReader = null;
1663: private MIFValueSetter[] fieldValueSetters;
1664: private Feature editFeature = null;
1665: private Feature originalFeature = null;
1666:
1667: private Writer(PrintStream mif, PrintStream mid, boolean append)
1668: throws IOException {
1669: innerReader = getFeatureReader();
1670:
1671: try {
1672: fieldValueSetters = getValueSetters();
1673: } catch (SchemaException e) {
1674: throw new IOException(e.getMessage());
1675: }
1676:
1677: outMif = mif;
1678: outMid = mid;
1679:
1680: try {
1681: if (!append) {
1682: outMif.println(exportHeader());
1683: }
1684: } catch (Exception e) {
1685: outMif = null;
1686: outMid = null;
1687: throw new IOException(e.getMessage());
1688: }
1689: }
1690:
1691: public FeatureType getFeatureType() {
1692: return featureType;
1693: }
1694:
1695: public Feature next() throws IOException {
1696: try {
1697: if (originalFeature != null) {
1698: writeFeature(originalFeature); // keep the original
1699: }
1700:
1701: if (innerReader.hasNext()) {
1702: originalFeature = innerReader.next(); // ;
1703: editFeature = featureType
1704: .duplicate(originalFeature);
1705: } else {
1706: originalFeature = null;
1707: editFeature = featureType.create(featureDefaults);
1708: }
1709:
1710: return editFeature;
1711: } catch (Exception e) {
1712: throw new IOException(e.toString());
1713: }
1714: }
1715:
1716: public void remove() throws IOException {
1717: if (editFeature == null) {
1718: throw new IOException("Current feature is null");
1719: }
1720:
1721: editFeature = null;
1722: originalFeature = null;
1723: }
1724:
1725: public void write() throws IOException {
1726: if (editFeature == null) {
1727: throw new IOException("Current feature is null");
1728: }
1729:
1730: try {
1731: writeFeature(editFeature);
1732: } catch (Exception e) {
1733: editFeature = null;
1734: throw new IOException("Can't write feature: "
1735: + e.toString());
1736: }
1737:
1738: editFeature = null;
1739: originalFeature = null;
1740: }
1741:
1742: public boolean hasNext() throws IOException {
1743: return innerReader.hasNext();
1744: }
1745:
1746: public void close() throws IOException {
1747: while (hasNext())
1748: next();
1749:
1750: try {
1751: if (originalFeature != null) {
1752: writeFeature(originalFeature); // keep the original
1753: }
1754: } catch (Exception e) {
1755: }
1756:
1757: innerReader.close();
1758: innerReader = null;
1759:
1760: try {
1761: if (outMif != null) {
1762: outMif.close();
1763: }
1764:
1765: if (outMid != null) {
1766: outMid.close();
1767: }
1768:
1769: copyFileAndDelete(mifFileOut, mifFile, true);
1770: copyFileAndDelete(midFileOut, midFile, true);
1771: } catch (IOException e) {
1772: } finally {
1773: outMid = null;
1774: outMif = null;
1775: }
1776: }
1777:
1778: protected void finalize() throws Throwable {
1779: close();
1780: super .finalize();
1781: }
1782:
1783: /**
1784: * Writes the given Feature to file
1785: *
1786: * @param f The feature to write
1787: *
1788: * @throws IOException if cannot access file for reading
1789: * @throws SchemaException if given Feature is not compatible with
1790: * MIFFile FeatureType. TODO: private
1791: */
1792: public void writeFeature(Feature f) throws IOException,
1793: SchemaException {
1794: if ((outMif == null) || (outMid == null)) {
1795: throw new IOException(
1796: "Output stream has not been opened for writing.");
1797: }
1798:
1799: Geometry theGeom = (geomFieldIndex >= 0) ? (Geometry) f
1800: .getAttribute(geomFieldIndex) : null;
1801: String outGeom = exportGeometry(theGeom);
1802:
1803: if (outGeom.equals("")) {
1804: throw new SchemaException("Unsupported geometry type: "
1805: + theGeom.getClass().getName());
1806: }
1807:
1808: outMif.println(outGeom);
1809:
1810: int col;
1811: String outBuf = "";
1812:
1813: try {
1814: for (col = 1; col < numAttribs; col++) {
1815: fieldValueSetters[col]
1816: .setValue(f.getAttribute(col));
1817:
1818: if (col > 1) {
1819: outBuf += chDelimiter;
1820: }
1821:
1822: outBuf += fieldValueSetters[col].getString();
1823: }
1824: } catch (Exception e) {
1825: throw new IOException("Error writing MID file: "
1826: + e.getMessage());
1827: }
1828:
1829: outMid.println(outBuf);
1830: }
1831:
1832: private String exportGeometry(Geometry geom) {
1833: // Style information is optional, so we will not export the default styles
1834: if ((geom == null) || (geom.isEmpty())) {
1835: return TYPE_NONE.toUpperCase();
1836: }
1837:
1838: if (geom instanceof Point) {
1839: return TYPE_POINT + " "
1840: + exportCoord(((Point) geom).getCoordinate());
1841: }
1842:
1843: if (geom instanceof LineString) {
1844: Coordinate[] coords = geom.getCoordinates();
1845:
1846: return TYPE_PLINE + " " + exportCoords(coords, false);
1847: }
1848:
1849: if (geom instanceof MultiPolygon) {
1850: MultiPolygon mpoly = (MultiPolygon) geom;
1851:
1852: int nPol = mpoly.getNumGeometries();
1853: int nRings = nPol;
1854:
1855: for (int i = 0; i < nPol; i++) {
1856: nRings += ((Polygon) mpoly.getGeometryN(i))
1857: .getNumInteriorRing();
1858: }
1859:
1860: String buf = TYPE_REGION + " " + (nRings);
1861:
1862: for (int i = 0; i < nPol; i++) {
1863: Polygon poly = (Polygon) mpoly.getGeometryN(i);
1864:
1865: buf += ("\n" + exportCoords(poly.getExteriorRing()
1866: .getCoordinates(), true));
1867:
1868: for (int inner = 0; inner < poly
1869: .getNumInteriorRing(); inner++) {
1870: buf += ("\n" + exportCoords(poly
1871: .getInteriorRingN(inner)
1872: .getCoordinates(), true));
1873: }
1874: }
1875:
1876: return buf;
1877: }
1878:
1879: if (geom instanceof Polygon) {
1880: Polygon poly = (Polygon) geom;
1881: int nRings = poly.getNumInteriorRing();
1882: String buf = TYPE_REGION + " " + (1 + nRings) + "\n";
1883: buf += exportCoords(poly.getExteriorRing()
1884: .getCoordinates(), true);
1885:
1886: for (int i = 0; i < nRings; i++) {
1887: buf += ("\n" + exportCoords(poly
1888: .getInteriorRingN(i).getCoordinates(), true));
1889: }
1890:
1891: return buf;
1892: }
1893:
1894: if (geom instanceof MultiLineString) {
1895: MultiLineString multi = (MultiLineString) geom;
1896: String buf = TYPE_PLINE + " Multiple "
1897: + multi.getNumGeometries();
1898:
1899: for (int i = 0; i < multi.getNumGeometries(); i++) {
1900: buf += ("\n" + exportCoords(((LineString) multi
1901: .getGeometryN(i)).getCoordinates(), false));
1902: }
1903:
1904: return buf;
1905: }
1906:
1907: return "";
1908: }
1909:
1910: /**
1911: * Renders a single coordinate
1912: *
1913: * @param coord The Coordinate object
1914: *
1915: * @return The coordinate as string
1916: */
1917: private String exportCoord(Coordinate coord) {
1918: return coord.x + " " + coord.y;
1919: }
1920:
1921: /**
1922: * Renders a coordinate list, prefixing it with the number of points
1923: * SkipLast is used for Polygons (in Mapinfo the last vertex of a
1924: * polygon is not the clone of first one)
1925: *
1926: * @param coords The coordinates to render
1927: * @param skipLast if true, a polygon coordinate list will be rendered
1928: *
1929: * @return the coordinate list as string
1930: */
1931: private String exportCoords(Coordinate[] coords,
1932: boolean skipLast) {
1933: int len = (skipLast) ? (coords.length - 1) : coords.length;
1934:
1935: String buf = String.valueOf(len);
1936:
1937: for (int i = 0; i < len; i++) {
1938: buf += ("\n" + exportCoord(coords[i]));
1939: }
1940:
1941: return buf;
1942: }
1943: }
1944: }
|