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