001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/io/mapinfoapi/MapInfoDataSource.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree.
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 http://www.geographie.uni-bonn.de/deegree/
007 and
008 lat/lon GmbH
009 http://lat-lon.de/
010
011 Additional copyright notes:
012 This class is based on the MapInfoDataSource class from the GeoTools project.
013 Geotools - OpenSource mapping toolkit
014 (C) 2002, Centre for Computational Geography
015
016 This library is free software; you can redistribute it and/or modify it under
017 the terms of the GNU Lesser General Public License as published by the Free
018 Software Foundation; either version 2.1 of the License, or (at your option)
019 any later version.
020 This library is distributed in the hope that it will be useful, but WITHOUT
021 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
022 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
023 details.
024 You should have received a copy of the GNU Lesser General Public License
025 along with this library; if not, write to the Free Software Foundation, Inc.,
026 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
027
028 Contact information:
029
030 lat/lon GmbH
031 Aennchenstr. 19, 53177 Bonn
032 Germany
033
034 Department of Geography, University of Bonn
035 Prof. Dr. Klaus Greve
036 Postfach 1147, 53001 Bonn
037 Germany
038
039 e-mail: info@deegree.org
040 ----------------------------------------------------------------------------*/
041 package org.deegree.io.mapinfoapi;
042
043 import java.io.BufferedReader;
044 import java.io.File;
045 import java.io.FileNotFoundException;
046 import java.io.FileReader;
047 import java.io.IOException;
048 import java.net.URL;
049 import java.util.ArrayList;
050 import java.util.StringTokenizer;
051
052 import org.deegree.datatypes.QualifiedName;
053 import org.deegree.datatypes.Types;
054 import org.deegree.framework.log.ILogger;
055 import org.deegree.framework.log.LoggerFactory;
056 import org.deegree.framework.util.CharsetUtils;
057 import org.deegree.framework.util.StringTools;
058 import org.deegree.model.feature.Feature;
059 import org.deegree.model.feature.FeatureCollection;
060 import org.deegree.model.feature.FeatureFactory;
061 import org.deegree.model.feature.FeatureProperty;
062 import org.deegree.model.feature.schema.FeatureType;
063 import org.deegree.model.feature.schema.PropertyType;
064 import org.deegree.model.filterencoding.Filter;
065 import org.deegree.model.spatialschema.JTSAdapter;
066 import org.deegree.ogcwebservices.wfs.operation.Query;
067
068 import com.vividsolutions.jts.geom.Coordinate;
069 import com.vividsolutions.jts.geom.Geometry;
070 import com.vividsolutions.jts.geom.GeometryFactory;
071 import com.vividsolutions.jts.geom.Polygon;
072 import com.vividsolutions.jts.geom.TopologyException;
073
074 /**
075 * TODO add documentation here
076 *
077 *
078 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
079 * @author last edited by: $Author: mschneider $
080 *
081 * @version $Revision: 18191 $, $Date: 2009-06-18 17:26:44 +0200 (Do, 18. Jun 2009) $
082 */
083 public class MapInfoDataSource {
084
085 private static ILogger LOG = LoggerFactory.getLogger( MapInfoDataSource.class );
086
087 public static final String TYPE_NONE = "none";
088
089 public static final String TYPE_POINT = "point";
090
091 public static final String TYPE_LINE = "line";
092
093 public static final String TYPE_PLINE = "pline";
094
095 public static final String TYPE_REGION = "region";
096
097 public static final String TYPE_ARC = "arc";
098
099 public static final String TYPE_TEXT = "text";
100
101 public static final String TYPE_RECT = "rectangle";
102
103 public static final String TYPE_ROUNDRECT = "rounded rectangle";
104
105 public static final String TYPE_ELLIPSE = "ellipse";
106
107 public static final String CLAUSE_SYMBOL = "SYMBOL";
108
109 public static final String CLAUSE_PEN = "PEN";
110
111 public static final String CLAUSE_SMOOTH = "SMOOTH";
112
113 public static final String CLAUSE_CENTER = "CENTER";
114
115 public static final String CLAUSE_BRUSH = "BRUSH";
116
117 public static final String CLAUSE_VERSION = "Version";
118
119 public static final String CLAUSE_CHARSET = "Charset";
120
121 public static final String CLAUSE_DELIMETER = "DELIMITER";
122
123 public static final String CLAUSE_UNIQUE = "UNIQUE";
124
125 public static final String CLAUSE_INDEX = "INDEX";
126
127 public static final String CLAUSE_COLUMNS = "COLUMNS";
128
129 private ArrayList<String> hColumnsNames;
130
131 private ArrayList<String> hColumnsTypes;
132
133 private ArrayList<String> hIndex;
134
135 private ArrayList<String> hUnique;
136
137 private FeatureType lineType = null;
138
139 private FeatureType pointType = null;
140
141 private FeatureType polygonType = null;
142
143 // Factory to use to build Geometries
144 private GeometryFactory geomFactory;
145
146 private String filename;
147
148 private String hCharset;
149
150 private String hDelimeter = "\t";
151
152 // Header information
153 private String hVersion;
154
155 // CoordsSys not supported
156 // Transform not supported
157 // Global variables (for the initial read)
158 private String line; // The current Line of the MIF file.
159
160 /**
161 * Creates a new MapInfoDataSource object.
162 *
163 * @param url
164 *
165 * @throws java.net.MalformedURLException
166 */
167 public MapInfoDataSource( URL url ) throws java.net.MalformedURLException {
168 try {
169 filename = java.net.URLDecoder.decode( url.getFile(), CharsetUtils.getSystemCharset() );
170 } catch ( Exception e ) {
171 throw new java.net.MalformedURLException( e.toString() );
172 }
173 geomFactory = new GeometryFactory();
174 }
175
176 /**
177 * Reads the MIF and MID files and returns a ArrayList of the Features they contain
178 *
179 * @return a ArrayList of features?
180 *
181 * @throws Exception
182 * if file doesn't exist or is not readable etc
183 */
184 protected ArrayList readMifMid()
185 throws Exception {
186 if ( filename == null ) {
187 throw new Exception( "Invalid filename passed to readMifMid" );
188 }
189
190 String mifFile = setExtension( filename, "MIF" );
191 String midFile = setExtension( filename, "MID" );
192
193 // Read files
194 try {
195 File mif = new File( mifFile );
196
197 if ( !mif.exists() ) {
198 mifFile = setExtension( filename, "mif" );
199 mif = new File( mifFile );
200
201 if ( !mif.exists() ) {
202 mifFile = setExtension( filename.toLowerCase(), "mif" );
203 mif = new File( mifFile );
204
205 if ( !mif.exists() ) {
206 mifFile = setExtension( filename.toUpperCase(), "MIF" );
207 mif = new File( mifFile );
208 } // and at that I'm out of guesses
209 }
210 }
211
212 File mid = new File( midFile );
213
214 if ( !mid.exists() ) {
215 midFile = setExtension( filename, "mid" );
216 mid = new File( midFile );
217
218 if ( !mid.exists() ) {
219 midFile = setExtension( filename.toLowerCase(), "mid" );
220 mid = new File( midFile );
221
222 if ( !mid.exists() ) {
223 midFile = setExtension( filename.toUpperCase(), "MID" );
224 mid = new File( midFile );
225 } // and at that I'm out of guesses
226 }
227 }
228
229 ArrayList features = readMifMid( new BufferedReader( new FileReader( mif ) ),
230 new BufferedReader( new FileReader( mid ) ) );
231
232 return features;
233 } catch ( FileNotFoundException fnfexp ) {
234 throw new Exception( "FileNotFoundException trying to read mif file : ", fnfexp );
235 }
236 }
237
238 /**
239 * @param filename
240 * @param ext
241 *
242 * @return
243 */
244 private String setExtension( String filename, String ext ) {
245 if ( ext.indexOf( "." ) == -1 ) {
246 ext = "." + ext;
247 }
248
249 if ( filename.lastIndexOf( "." ) == -1 ) {
250 return filename + ext;
251 }
252
253 return filename.substring( 0, filename.lastIndexOf( "." ) ) + ext;
254 }
255
256 /**
257 * This private method constructs the factories used to create the Feature, and Geometries as
258 * they are read It takes it's setup values from the value of the COLUMNS clause in the MIF file
259 *
260 * @throws Exception
261 */
262 private void setUpFactories()
263 throws Exception {
264 // Go through each column name, and set up an attribute for each one
265 ArrayList<PropertyType> colAttribs = new ArrayList<PropertyType>( hColumnsNames.size() );
266
267 // Add attributes for each column
268 // Iterator it = hColumns.keySet().iterator();
269 for ( int i = 0; i < hColumnsNames.size(); i++ ) {
270 String type = hColumnsTypes.get( i ).toLowerCase();
271 int typeClass = -999;
272
273 if ( type.equals( "float" ) || type.startsWith( "decimal" ) ) {
274 typeClass = Types.NUMERIC;
275 hColumnsTypes.set( i, "Double" );
276 } else if ( type.startsWith( "char" ) ) {
277 typeClass = Types.VARCHAR;
278 hColumnsTypes.set( i, "String" );
279 } else if ( type.equals( "integer" ) || type.equals( "smallint" ) ) {
280 typeClass = Types.INTEGER;
281 hColumnsTypes.set( i, "Integer" );
282 } else {
283 typeClass = Types.VARCHAR;
284 hColumnsTypes.set( i, "String" );
285 }
286
287 PropertyType ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( hColumnsNames.get( i ) ),
288 typeClass, true );
289 colAttribs.add( ftp );
290 }
291
292 PropertyType ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, true );
293
294 // Add default Geometry attribute type
295 colAttribs.add( 0, ftp );
296
297 // create point feature Type & factory
298 try {
299 PropertyType[] ftps = colAttribs.toArray( new PropertyType[colAttribs.size()] );
300 pointType = FeatureFactory.createFeatureType( filename.toString() + "_point", false, ftps );
301 } catch ( Exception schexp ) {
302 throw new Exception( "SchemaException setting up point factory : ", schexp );
303 }
304
305 // Set up Line factory
306 // Add default attribute type
307 ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, true );
308 colAttribs.set( 0, ftp );
309
310 // create line feature Type & factory
311 try {
312 PropertyType[] ftps = colAttribs.toArray( new PropertyType[colAttribs.size()] );
313 lineType = FeatureFactory.createFeatureType( filename.toString() + "_line", false, ftps );
314 } catch ( Exception schexp ) {
315 throw new Exception( "SchemaException setting up line factory : ", schexp );
316 }
317
318 // Set up Polygon factory
319 // Add default attribute type
320 ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, true );
321 colAttribs.set( 0, ftp );
322
323 // create polygon feature Type & factory
324 try {
325 PropertyType[] ftps = colAttribs.toArray( new PropertyType[colAttribs.size()] );
326 polygonType = FeatureFactory.createFeatureType( filename.toString() + "_poly", false, ftps );
327 } catch ( Exception schexp ) {
328 throw new Exception( "SchemaException setting up polygon factory : ", schexp );
329 }
330 }
331
332 /**
333 * Reads an entire MID/MIF file. (Two files, actually, separately opened)
334 *
335 * @param mifReader
336 * An opened BufferedReader to the MIF file.
337 * @param midReader
338 * An opened BufferedReader to the MID file.
339 *
340 * @return
341 * @throws Exception
342 */
343 private ArrayList readMifMid( BufferedReader mifReader, BufferedReader midReader )
344 throws Exception {
345 // Read the MIF header
346 readMifHeader( mifReader );
347
348 // Set up factories
349 setUpFactories();
350
351 ArrayList<Feature> features = new ArrayList<Feature>( 100 );
352
353 // Start by reading first line
354 try {
355 line = readMifLine( mifReader );
356 } catch ( IOException ioexp ) {
357 throw new Exception( "No data at start of file", ioexp );
358 }
359
360 Feature feature;
361
362 // Read each object in the MIF file
363 while ( ( feature = readObject( mifReader, midReader ) ) != null ) {
364 // Figure out which type of feature it is
365 // Add to relevent ArrayList
366 features.add( feature );
367 }
368
369 return features;
370 }
371
372 /**
373 * Reads the header from the given MIF file stream
374 *
375 * @param mifReader
376 *
377 * @throws Exception
378 */
379 private void readMifHeader( BufferedReader mifReader )
380 throws Exception {
381 try {
382 while ( ( readMifLine( mifReader ) != null ) && !line.trim().equalsIgnoreCase( "DATA" ) ) {
383 if ( clause( line ).equalsIgnoreCase( CLAUSE_VERSION ) ) {
384 // Read Version clause
385 hVersion = line.trim().substring( line.trim().indexOf( ' ' ) ).trim();
386 LOG.logDebug( "version [" + hVersion + "]" );
387 }
388
389 if ( clause( line ).equalsIgnoreCase( CLAUSE_CHARSET ) ) {
390 // Read Charset clause
391 // hCharset = line.replace('\"','
392 // ').trim().substring(line.trim().indexOf(' ')).trim();
393 hCharset = remainder( line ).replace( '"', ' ' ).trim();
394 LOG.logDebug( "Charset [" + hCharset + "]" );
395 }
396
397 if ( clause( line ).equalsIgnoreCase( CLAUSE_DELIMETER ) ) {
398 // Read Delimeter clause
399 hDelimeter = line.replace( '\"', ' ' ).trim().substring( line.trim().indexOf( ' ' ) ).trim();
400 LOG.logDebug( "delimiter [" + hDelimeter + "]" );
401 }
402
403 if ( clause( line ).equalsIgnoreCase( CLAUSE_UNIQUE ) ) {
404 // Read Unique clause
405 StringTokenizer st = new StringTokenizer( line.trim().substring( line.trim().indexOf( ' ' ) ), "," );
406 hUnique = new ArrayList<String>();
407 LOG.logDebug( "Unique cols " );
408
409 while ( st.hasMoreTokens() ) {
410 String uniq = st.nextToken();
411 LOG.logDebug( "\t" + uniq );
412 hUnique.add( uniq );
413 }
414 }
415
416 if ( clause( line ).equalsIgnoreCase( CLAUSE_INDEX ) ) {
417 // Read Index clause
418 StringTokenizer st = new StringTokenizer( line.trim().substring( line.trim().indexOf( ' ' ) ), "," );
419 hIndex = new ArrayList<String>( st.countTokens() );
420 LOG.logDebug( "Indexes" );
421
422 while ( st.hasMoreTokens() ) {
423 String index = st.nextToken();
424 LOG.logDebug( "\t" + index );
425 hIndex.add( index );
426 }
427 }
428
429 if ( clause( line ).equalsIgnoreCase( CLAUSE_COLUMNS ) ) {
430 // Read Columns clause
431 int cols = 0;
432
433 try {
434 cols = Integer.parseInt( remainder( line ) );
435 } catch ( NumberFormatException nfexp ) {
436 LOG.logError( "bad number of colums ", nfexp );
437 }
438
439 // Read each of the columns
440 hColumnsNames = new ArrayList<String>( cols );
441 hColumnsTypes = new ArrayList<String>( cols );
442
443 for ( int i = 0; i < cols; i++ ) {
444 line = readMifLine( mifReader );
445
446 // StringTokenizer st = new
447 // StringTokenizer(line.trim().substring(line.trim().indexOf('
448 // ')), " ");
449 String name = clause( line );
450 String value = remainder( line );
451
452 LOG.logDebug( "column name " + name + " value " + value );
453
454 hColumnsNames.add( name );
455 hColumnsTypes.add( value );
456 }
457 }
458 }
459 } catch ( IOException ioexp ) {
460 throw new Exception( "IOException reading MIF header : " + ioexp.getMessage() );
461 }
462 }
463
464 /**
465 * A 'Clause' is stored as a single string at the start of a line. This rips the clause name out
466 * of the given line.
467 *
468 * @param line
469 *
470 * @return
471 */
472 private String clause( String line ) {
473 return clause( line, ' ' );
474 }
475
476 /**
477 * @param line
478 * @param delimiter
479 *
480 * @return
481 */
482 private String clause( String line, char delimiter ) {
483 line = line.trim();
484
485 int index = line.indexOf( delimiter );
486
487 if ( index == -1 ) {
488 return line;
489 }
490 return line.substring( 0, index ).trim();
491 }
492
493 /**
494 * returns the last word of the string
495 *
496 * @param line
497 *
498 * @return the last word of the string
499 */
500 private String remainder( String line ) {
501 return remainder( line, ' ' );
502 }
503
504 /**
505 * @param line
506 * @param delimiter
507 *
508 * @return
509 */
510 private String remainder( String line, char delimiter ) {
511 line = line.trim();
512
513 int index = line.lastIndexOf( delimiter );
514
515 if ( index == -1 ) {
516 return "";
517 }
518 return line.substring( index ).trim();
519 }
520
521 /**
522 * Reads the next line in the reader, ignoring lines which are nothing but whitespace. Sets the
523 * global 'line' variable to the currently read line
524 *
525 * @param reader
526 *
527 * @return
528 * @throws IOException
529 * @throws Exception
530 */
531 private String readMifLine( BufferedReader reader )
532 throws IOException, Exception {
533 do {
534 line = reader.readLine();
535
536 if ( line == null ) {
537 return null;
538 }
539
540 if ( isShadingClause( line ) ) {
541 LOG.logDebug( "going to process shading" );
542
543 // processShading(line);
544 line = " ";
545 }
546 } while ( line.trim().length() == 0 );
547
548 line = line.trim();
549
550 // LOG.logDebug("returning line " + line);
551 return line;
552 }
553
554 /**
555 * Reads a single MIF Object (Point, Line, Region, etc.) as a Feature
556 *
557 * @param mifReader
558 * @param midReader
559 *
560 * @return
561 * @throws Exception
562 */
563 private Feature readObject( BufferedReader mifReader, BufferedReader midReader )
564 throws Exception {
565 Feature feature = null;
566
567 // LOG.logDebug("line = " + line);
568 // examine The current line
569 if ( line == null ) {
570 return null;
571 }
572
573 int index = line.indexOf( ' ' );
574
575 if ( index == -1 ) {
576 index = line.length();
577 }
578
579 if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_POINT ) ) {
580 // Read point data
581 feature = readPointObject( mifReader, midReader );
582 } else if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_LINE ) ) {
583 // Read line data
584 feature = readLineObject( mifReader, midReader );
585 } else if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_PLINE ) ) {
586 // Read pline data
587 feature = readPLineObject( mifReader, midReader );
588 } else if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_REGION ) ) {
589 // Read region data
590 feature = readRegionObject( mifReader, midReader );
591 } else {
592 LOG.logDebug( line + " unknown object in mif reader" );
593 }
594
595 return feature;
596 }
597
598 /**
599 * Reads Point information from the MIF stream
600 *
601 * @param mifReader
602 * @param midReader
603 *
604 * @return
605 * @throws Exception
606 */
607 private Feature readPointObject( BufferedReader mifReader, BufferedReader midReader )
608 throws Exception {
609 Feature feature = null;
610
611 StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ), "," );
612
613 try {
614 double x = Double.parseDouble( st.nextToken() );
615 double y = Double.parseDouble( st.nextToken() );
616
617 // Construct Geomtry
618 Geometry pointGeom = geomFactory.createPoint( new Coordinate( x, y ) );
619
620 // Read next line
621 readMifLine( mifReader );
622
623 // Hashtable shading = readShading(mifReader);
624 // Shading is not included, as null feature attributes are not
625 // supported yet
626 ArrayList<Object> midValues = readMid( midReader );
627
628 // midValues.putAll(shading);
629 // Create Feature
630 feature = buildFeature( pointType, pointGeom, midValues );
631
632 LOG.logDebug( "Built point feature : " + x + " " + y );
633 } catch ( NumberFormatException nfexp ) {
634 throw new Exception( "Exception reading Point data from MIF file : ", nfexp );
635 } catch ( IOException ioexp ) {
636 throw new Exception( "IOException reading point data : ", ioexp );
637 }
638
639 return feature;
640 }
641
642 /**
643 * Reads Line information from the MIF stream
644 *
645 * @param mifReader
646 * @param midReader
647 *
648 * @return
649 * @throws Exception
650 */
651 private Feature readLineObject( BufferedReader mifReader, BufferedReader midReader )
652 throws Exception {
653 Feature feature = null;
654
655 StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ), "," );
656
657 try {
658 double x1 = Double.parseDouble( st.nextToken() );
659 double y1 = Double.parseDouble( st.nextToken() );
660 double x2 = Double.parseDouble( st.nextToken() );
661 double y2 = Double.parseDouble( st.nextToken() );
662
663 // Construct Geomtry
664 Coordinate[] cPoints = new Coordinate[2];
665 cPoints[0] = new Coordinate( x1, y1 );
666 cPoints[1] = new Coordinate( x2, y2 );
667
668 Geometry lineGeom = geomFactory.createLineString( cPoints );
669
670 // Read next line
671 readMifLine( mifReader );
672
673 // Hashtable shading = readShading(mifReader);
674 // Shading is not included, as null feature attributes are not
675 // supported yet
676 ArrayList<Object> midValues = readMid( midReader );
677
678 // midValues.putAll(shading);
679 // Create Feature
680 feature = buildFeature( lineType, lineGeom, midValues );
681
682 LOG.logDebug( "Built line feature : " + x1 + " " + y1 + " - " + x2 + " " + y2 );
683 } catch ( NumberFormatException nfexp ) {
684 throw new Exception( "Exception reading Point data from MIF file : " + nfexp.getMessage() );
685 } catch ( IOException ioexp ) {
686 throw new Exception( "IOException reading point data : " + ioexp.getMessage() );
687 }
688
689 return feature;
690 }
691
692 /**
693 * Reads Multi-Line (PLine) information from the MIF stream
694 *
695 * @param mifReader
696 * @param midReader
697 *
698 * @return
699 * @throws Exception
700 */
701 private Feature readPLineObject( BufferedReader mifReader, BufferedReader midReader )
702 throws Exception {
703 Feature feature = null;
704
705 StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ) );
706
707 try {
708 int numsections = 1;
709
710 if ( st.hasMoreTokens() && st.nextToken().trim().equalsIgnoreCase( "MULTIPLE" ) ) {
711 numsections = Integer.parseInt( st.nextToken() );
712 }
713
714 // A ArrayList of coordinates
715 ArrayList<Coordinate> coords = new ArrayList<Coordinate>( numsections );
716
717 // Read each polygon
718 for ( int i = 0; i < numsections; i++ ) {
719 // Read line (number of points
720 int numpoints = Integer.parseInt( readMifLine( mifReader ) );
721
722 // Read each point
723 for ( int p = 0; p < numpoints; p++ ) {
724 StringTokenizer pst = new StringTokenizer( readMifLine( mifReader ) );
725 double x = Double.parseDouble( pst.nextToken() );
726 double y = Double.parseDouble( pst.nextToken() );
727 coords.add( new Coordinate( x, y ) );
728 }
729 }
730
731 Geometry plineGeom = geomFactory.createLineString( coords.toArray( new Coordinate[coords.size()] ) );
732
733 // Read next line
734 readMifLine( mifReader );
735
736 // Hashtable shading = readShading(mifReader);
737 // Shading is not included, as null feature attributes are not
738 // supported yet
739 ArrayList<Object> midValues = readMid( midReader );
740
741 // midValues.putAll(shading);
742 // Create Feature
743 feature = buildFeature( lineType, plineGeom, midValues );
744
745 LOG.logDebug( "Read polyline (" + coords.size() + ")" );
746 } catch ( NumberFormatException nfexp ) {
747 throw new Exception( "Exception reading Point data from MIF file : " + nfexp.getMessage() );
748 } catch ( IOException ioexp ) {
749 throw new Exception( "IOException reading point data : " + ioexp.getMessage() );
750 }
751
752 return feature;
753 }
754
755 /**
756 * Reads Region (Polygon) information from the MIF stream
757 *
758 * @param mifReader
759 * @param midReader
760 *
761 * @return
762 * @throws Exception
763 */
764 private Feature readRegionObject( BufferedReader mifReader, BufferedReader midReader )
765 throws Exception {
766 Feature feature = null;
767
768 StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ) );
769
770 try {
771 int numpolygons = Integer.parseInt( st.nextToken() );
772
773 // A ArrayList of polygons
774 ArrayList<Polygon> polys = new ArrayList<Polygon>( numpolygons );
775
776 // Read each polygon
777 for ( int i = 0; i < numpolygons; i++ ) {
778 // Read number of points
779 int numpoints = Integer.parseInt( readMifLine( mifReader ) );
780 ArrayList<Coordinate> coords = new ArrayList<Coordinate>( numpoints );
781
782 // Read each point
783 for ( int p = 0; p < numpoints; p++ ) {
784 StringTokenizer pst = new StringTokenizer( readMifLine( mifReader ) );
785 double x = Double.parseDouble( pst.nextToken() );
786 double y = Double.parseDouble( pst.nextToken() );
787 coords.add( new Coordinate( x, y ) );
788 }
789
790 // Create polygon from points
791 coords.add( new Coordinate( ( coords.get( 0 ) ).x, ( coords.get( 0 ) ).y ) );
792
793 try {
794 Polygon pol = geomFactory.createPolygon(
795 geomFactory.createLinearRing( coords.toArray( new Coordinate[coords.size()] ) ),
796 null );
797
798 // Add to ArrayList
799 polys.add( pol );
800 } catch ( TopologyException topexp ) {
801 throw new Exception( "TopologyException reading Region polygon : ", topexp );
802 }
803 }
804
805 Geometry polyGeom = geomFactory.createMultiPolygon( polys.toArray( new Polygon[polys.size()] ) );
806
807 // Read next line
808 readMifLine( mifReader );
809
810 // Hashtable shading = readShading(mifReader);
811 // Shading is not included, as null feature attributes are not
812 // supported yet
813 ArrayList<Object> midValues = readMid( midReader );
814
815 // midValues.putAll(shading);
816 // Create Feature
817 feature = buildFeature( polygonType, polyGeom, midValues );
818
819 LOG.logDebug( "Read Region (" + polys.size() + ")" );
820 } catch ( NumberFormatException nfexp ) {
821 throw new Exception( "Exception reading Point data from MIF file : ", nfexp );
822 } catch ( IOException ioexp ) {
823 throw new Exception( "IOException reading point data : ", ioexp );
824 }
825
826 return feature;
827 }
828
829 /**
830 * Builds a complete Feature object using the given FeatureType, with the Geometry geom, and the
831 * given attributes.
832 *
833 * @param featureType
834 * The FeatureType to use to constuct the Feature
835 * @param geom
836 * The Geometry to use as the default Geometry
837 * @param attribs
838 * The attibutes to use as the Feature's attributes (Attributes must be set up in the
839 * FeatureType)
840 *
841 * @return A fully-formed Feature
842 *
843 * @throws Exception
844 */
845 private Feature buildFeature( FeatureType featureType, Geometry geom, ArrayList<Object> attribs )
846 throws Exception {
847 int numAttribs = featureType.getProperties().length;
848
849 // add geometry to the attributes
850 attribs.add( 0, JTSAdapter.wrap( geom ) );
851
852 if ( numAttribs != attribs.size() ) {
853 throw new Exception( "wrong number of attributes passed to buildFeature.\n" + "expected " + numAttribs
854 + " got " + attribs.size() );
855 }
856
857 // Create Feature
858 FeatureProperty[] fp = new FeatureProperty[attribs.size()];
859 PropertyType[] ftp = featureType.getProperties();
860 for ( int i = 0; i < fp.length; i++ ) {
861 fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), attribs.get( i ) );
862 }
863 try {
864 return FeatureFactory.createFeature( "id", featureType, fp );
865 } catch ( Exception ifexp ) {
866 throw new Exception( "Exception creating feature : ", ifexp );
867 }
868 }
869
870 /**
871 * Reads a single line of the given MID file stream, and returns a hashtable of the data in it,
872 * keyed byt he keys in the hColumns hash
873 *
874 * @param midReader
875 *
876 * @return
877 * @throws Exception
878 */
879 private ArrayList<Object> readMid( BufferedReader midReader )
880 throws Exception {
881 ArrayList<Object> midValues = new ArrayList<Object>();
882
883 if ( midReader == null ) {
884 return midValues;
885 }
886
887 // The delimeter is a single delimiting character
888 String midLine = "";
889
890 try {
891 midLine = midReader.readLine();
892 LOG.logDebug( "Read MID " + midLine );
893 } catch ( IOException ioexp ) {
894 throw new Exception( "IOException reading MID file" );
895 }
896
897 // read MID tokens
898 int col = 0;
899 StringTokenizer quotes = new StringTokenizer( midLine, "\"" );
900
901 while ( quotes.hasMoreTokens() ) {
902 StringTokenizer delimeters = new StringTokenizer( quotes.nextToken(), hDelimeter + "\0" );
903
904 // Read each delimited value into the ArrayList
905 while ( delimeters.hasMoreTokens() ) {
906 String token = delimeters.nextToken();
907 String type = hColumnsTypes.get( col++ );
908 addAttribute( type, token, midValues );
909 }
910
911 // Store the whole of the next bit (it's a quoted string)
912 if ( quotes.hasMoreTokens() ) {
913 String token = quotes.nextToken();
914 String type = hColumnsTypes.get( col++ );
915 addAttribute( type, token, midValues );
916 LOG.logDebug( "adding " + token );
917 }
918 }
919
920 return midValues;
921 }
922
923 /**
924 * @param type
925 * @param token
926 * @param midValues
927 */
928 private void addAttribute( String type, String token, ArrayList<Object> midValues ) {
929 if ( type.equals( "String" ) ) {
930 midValues.add( token );
931 } else if ( type.equals( "Double" ) ) {
932 try {
933 token = StringTools.validateString( token, "," );
934 midValues.add( new Double( token ) );
935 } catch ( NumberFormatException nfe ) {
936 LOG.logError( "Bad double " + token, nfe );
937 midValues.add( new Double( 0.0 ) );
938 }
939 } else if ( type.equals( "Integer" ) ) {
940 try {
941 // token = StringExtend.validateString( token, "," );
942 midValues.add( new Integer( token ) );
943 } catch ( NumberFormatException nfe ) {
944 LOG.logError( "Bad integer " + token, nfe );
945 midValues.add( new Integer( 0 ) );
946 }
947 } else {
948 LOG.logDebug( "Unknown type " + type );
949 }
950 }
951
952 /**
953 * Reads the shading information at the end of Object data
954 *
955 * @param line
956 *
957 * @throws Exception
958 */
959
960 // private void processShading(String line) throws Exception {
961 // int color;
962 // int r;
963 // int g;
964 // int b;
965 //
966 // if (line == null) {
967 // return;
968 // }
969 //
970 // String shadeType = line.toLowerCase();
971 // String name = clause(shadeType, '(');
972 // String settings = remainder(shadeType, '(');
973 // StringTokenizer st = new StringTokenizer(settings, "(),");
974 // String[] values = new String[st.countTokens()];
975 //
976 // for (int i = 0; st.hasMoreTokens(); i++) {
977 // values[i] = st.nextToken();
978 // }
979 //
980 // if (name.equals("pen")) {
981 // try {
982 //
983 // stroke.setWidth(filterFactory.createLiteralExpression(new
984 // Integer(values[0])));
985 //
986 // int pattern = Integer.parseInt(values[1]);
987 //
988 // stroke.setDashArray(MifStyles.getPenPattern(new Integer(pattern)));
989 // color = Integer.parseInt(values[2]);
990 //
991 // String rgb = Integer.toHexString(color);
992 //
993 // stroke.setColor(filterFactory.createLiteralExpression(rgb));
994 // } catch (Exception nfe) {
995 // throw new Exception("Error setting up pen", nfe);
996 // }
997 //
998 // return;
999 // } else if (name.equals("brush")) {
1000 // LOG.logDebug("setting new brush " + settings);
1001 //
1002 // int pattern = Integer.parseInt(values[0]);
1003 // LOG.logDebug("pattern = " + pattern);
1004 //
1005 // Graphic dg = styleFactory.getDefaultGraphic();
1006 // dg.addExternalGraphic(MifStyles.getBrushPattern(new Integer(pattern)));
1007 // stroke.setGraphicFill(dg);
1008 // color = Integer.parseInt(values[1]);
1009 //
1010 // String rgb = Integer.toHexString(color);
1011 // LOG.logDebug("color " + color + " -> " + rgb);
1012 // fill.setColor(filterFactory.createLiteralExpression(rgb)); // foreground
1013 //
1014 // if (values.length == 3) { // optional parameter
1015 // color = Integer.parseInt(values[2]);
1016 // rgb = Integer.toHexString(color);
1017 // LOG.logDebug("color " + color + " -> " + rgb);
1018 //
1019 // fill.setBackgroundColor(filterFactory.createLiteralExpression(rgb)); //
1020 // background
1021 // } else {
1022 // fill.setBackgroundColor((Expression) null);
1023 // }
1024 // } else if (name.equals("center")) {
1025 // LOG.logDebug("setting center " + settings);
1026 // } else if (name.equals("smooth")) {
1027 // LOG.logDebug("setting smooth on");
1028 // } else if (name.equals("symbol")) {
1029 // LOG.logDebug("setting symbol " + settings);
1030 //
1031 // Mark symbol = null;
1032 // ExternalGraphic eg = null;
1033 //
1034 // if (values.length == 3) { // version 3.0
1035 //
1036 // //symbol = symbols.get(new Integer(symNumb));
1037 // } else if (values.length == 6) {}
1038 // else if (values.length == 4) { // custom bitmap
1039 // eg = styleFactory.createExternalGraphic("CustSymb/" +
1040 // values[0],"image/unknown"); // hack!
1041 //
1042 // } else {
1043 // LOGGER.info("unexpected symbol style " + name + settings);
1044 // }
1045 // } else if (name.equals("font")) {
1046 // LOG.logDebug("setting font " + settings);
1047 // } else {
1048 // LOG.logDebug("unknown styling directive " + name + settings);
1049 // }
1050 //
1051 // return;
1052 // }
1053 /**
1054 * Test whether the given line contains a known shading clause keyword (PEN, STYLE, etc.)
1055 *
1056 * @param line
1057 *
1058 * @return
1059 */
1060 private boolean isShadingClause( String line ) {
1061 line = line.toUpperCase();
1062
1063 boolean ret = ( ( line.indexOf( CLAUSE_PEN ) != -1 ) || ( line.indexOf( CLAUSE_SYMBOL ) != -1 )
1064 || ( line.indexOf( CLAUSE_SMOOTH ) != -1 ) || ( line.indexOf( CLAUSE_CENTER ) != -1 ) || line.indexOf( CLAUSE_BRUSH ) != -1 );
1065
1066 return ret;
1067 }
1068
1069 /**
1070 * Loads features from the datasource into the passed collection, based on the passed filter.
1071 * Note that all data sources must support this method at a minimum.
1072 *
1073 * @param collection
1074 * The collection to put the features into.
1075 * @param query
1076 * contains info about request of which features to retrieve.
1077 *
1078 * @throws Exception
1079 * For all data source errors.
1080 */
1081 public void getFeatures( FeatureCollection collection, Query query )
1082 throws Exception {
1083 Filter filter = null;
1084
1085 if ( query != null ) {
1086 filter = query.getFilter();
1087 }
1088
1089 ArrayList features = readMifMid();
1090
1091 for ( int i = 0; i < features.size(); i++ ) {
1092 if ( ( filter == null ) || filter.evaluate( (Feature) features.get( i ) ) ) {
1093 collection.add( (Feature) features.get( i ) );
1094 }
1095 }
1096 }
1097
1098 /**
1099 * Retrieves the featureType that features extracted from this datasource will be created with.
1100 * TODO: implement this method.
1101 *
1102 * @return <code>null</code>
1103 */
1104 public FeatureType getSchema() {
1105 return null;
1106 }
1107 }