001    //$HeadURL: https://sushibar/svn/deegree/base/trunk/resources/eclipse/svn_classfile_header_template.xml $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    
037    package org.deegree.io.mapinfoapi;
038    
039    import static java.io.StreamTokenizer.TT_EOF;
040    import static java.io.StreamTokenizer.TT_EOL;
041    import static java.lang.Double.parseDouble;
042    import static java.lang.Integer.parseInt;
043    import static java.nio.charset.Charset.defaultCharset;
044    import static java.nio.charset.Charset.forName;
045    import static org.deegree.datatypes.Types.BOOLEAN;
046    import static org.deegree.datatypes.Types.DATE;
047    import static org.deegree.datatypes.Types.DOUBLE;
048    import static org.deegree.datatypes.Types.FLOAT;
049    import static org.deegree.datatypes.Types.GEOMETRY;
050    import static org.deegree.datatypes.Types.INTEGER;
051    import static org.deegree.datatypes.Types.VARCHAR;
052    import static org.deegree.model.feature.FeatureFactory.createFeature;
053    import static org.deegree.model.feature.FeatureFactory.createFeatureCollection;
054    import static org.deegree.model.feature.FeatureFactory.createFeatureProperty;
055    import static org.deegree.model.feature.FeatureFactory.createFeatureType;
056    import static org.deegree.model.feature.FeatureFactory.createGeometryPropertyType;
057    import static org.deegree.model.feature.FeatureFactory.createSimplePropertyType;
058    import static org.deegree.model.spatialschema.GeometryFactory.createPoint;
059    
060    import java.io.File;
061    import java.io.FileInputStream;
062    import java.io.FileNotFoundException;
063    import java.io.IOException;
064    import java.io.InputStreamReader;
065    import java.io.StreamTokenizer;
066    import java.io.UnsupportedEncodingException;
067    import java.net.URI;
068    import java.net.URISyntaxException;
069    import java.nio.charset.Charset;
070    import java.text.DateFormat;
071    import java.text.ParseException;
072    import java.text.SimpleDateFormat;
073    import java.util.Date;
074    import java.util.HashMap;
075    import java.util.HashSet;
076    import java.util.LinkedList;
077    
078    import org.deegree.datatypes.QualifiedName;
079    import org.deegree.framework.log.ILogger;
080    import org.deegree.framework.log.LoggerFactory;
081    import org.deegree.framework.util.Pair;
082    import org.deegree.model.crs.CoordinateSystem;
083    import org.deegree.model.feature.Feature;
084    import org.deegree.model.feature.FeatureCollection;
085    import org.deegree.model.feature.FeatureProperty;
086    import org.deegree.model.feature.schema.FeatureType;
087    import org.deegree.model.feature.schema.PropertyType;
088    import org.deegree.model.spatialschema.Curve;
089    import org.deegree.model.spatialschema.MultiPoint;
090    import org.deegree.model.spatialschema.MultiSurface;
091    import org.deegree.model.spatialschema.Point;
092    import org.deegree.model.spatialschema.Surface;
093    
094    /**
095     * <code>MapInfoReader</code> is a new implementation of a reader for the MID/MIF format, based on
096     * the MapInfo 8.5 documentation.
097     *
098     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
099     * @author last edited by: $Author:$
100     *
101     * @version $Revision:$, $Date:$
102     */
103    public class MapInfoReader {
104    
105        private static final ILogger LOG = LoggerFactory.getLogger( MapInfoReader.class );
106    
107        private static final URI APPNS;
108    
109        private StreamTokenizer mif;
110    
111        private StreamTokenizer mid;
112    
113        private FeatureType featureType;
114    
115        private char delimiter;
116    
117        private File mifFile;
118    
119        private File midFile;
120    
121        private FeatureCollection featureCollection;
122    
123        private LinkedList<Feature> features;
124    
125        private MIFGeometryParser parser;
126    
127        private MIFStyleParser styleParser;
128    
129        private HashMap<String, HashSet<HashMap<String, String>>> styles;
130    
131        private boolean featureTypeParsed, featuresParsed;
132    
133        static {
134            URI u = null;
135            try {
136                u = new URI( "http://www.deegree.org/app" );
137            } catch ( URISyntaxException e ) {
138                // eat it
139            }
140            APPNS = u;
141        }
142    
143        /**
144         * @param baseName
145         *            the base name of the file(s), may also end with .mif/.mid
146         * @throws UnsupportedEncodingException
147         * @throws FileNotFoundException
148         */
149        public MapInfoReader( String baseName ) throws FileNotFoundException, UnsupportedEncodingException {
150            if ( baseName.toLowerCase().endsWith( ".mid" ) || baseName.toLowerCase().endsWith( ".mif" ) ) {
151                baseName = baseName.substring( 0, baseName.length() - 4 );
152                LOG.logDebug( "Reading base name of ", baseName );
153            }
154    
155            midFile = new File( baseName + ".mid" );
156            if ( !midFile.exists() ) {
157                midFile = new File( baseName + ".MID" );
158            }
159            mifFile = new File( baseName + ".mif" );
160            if ( !mifFile.exists() ) {
161                mifFile = new File( baseName + ".MIF" );
162            }
163    
164            mif = getMIFTokenizer( mifFile, null );
165            mid = getMIDTokenizer( midFile, null );
166        }
167    
168        /**
169         * Was that so hard?
170         *
171         * @param chars
172         * @param tok
173         */
174        public static void wordChars( StreamTokenizer tok, char... chars ) {
175            for ( char c : chars ) {
176                tok.wordChars( c, c );
177            }
178        }
179    
180        /**
181         * Was that so hard?
182         *
183         * @param chars
184         * @param tok
185         */
186        public static void quoteChars( StreamTokenizer tok, char... chars ) {
187            for ( char c : chars ) {
188                tok.quoteChar( c );
189            }
190        }
191    
192        /**
193         * Was that so hard?
194         *
195         * @param chars
196         * @param tok
197         */
198        public static void whitespaceChars( StreamTokenizer tok, char... chars ) {
199            for ( char c : chars ) {
200                tok.whitespaceChars( c, c );
201            }
202        }
203    
204        // applies heuristics to work around "try what your mapinfo says" in the documentation
205        private static Charset getCharset( String charset ) {
206            if ( charset == null ) {
207                charset = defaultCharset().name();
208                LOG.logDebug( "Parsing with default charset " + charset + " until charset directive is found." );
209            } else if ( charset.equals( "WindowsLatin1" ) ) {
210                LOG.logDebug( "Parsing with charset " + charset + ", interpreting it as iso-8859-1." );
211                charset = "iso-8859-1";
212            } else {
213                LOG.logDebug( "Parsing with unknown charset " + charset + ", hoping Java knows the name. " );
214            }
215    
216            return forName( charset );
217        }
218    
219        /**
220         * @param file
221         * @param charset
222         * @return a stream tokenizer with some specific settings for reading mif files
223         * @throws FileNotFoundException
224         * @throws UnsupportedEncodingException
225         */
226        public static StreamTokenizer getMIFTokenizer( File file, String charset )
227                                throws FileNotFoundException, UnsupportedEncodingException {
228            charset = getCharset( charset ).name();
229    
230            StreamTokenizer tok = new StreamTokenizer( new InputStreamReader( new FileInputStream( file ), charset ) );
231    
232            tok.resetSyntax();
233            tok.eolIsSignificant( false );
234            tok.lowerCaseMode( true );
235            tok.slashSlashComments( false );
236            tok.slashStarComments( false );
237            tok.wordChars( 'a', 'z' );
238            tok.wordChars( 'A', 'Z' );
239            tok.wordChars( '\u00a0', '\u00ff' );
240            tok.wordChars( '0', '9' );
241            wordChars( tok, '.', '-', '_' );
242            quoteChars( tok, '\'', '"' );
243            whitespaceChars( tok, ' ', '\n', '\r', '\f', '\t', '(', ')', ',' );
244    
245            return tok;
246        }
247    
248        /**
249         * @param file
250         * @param charset
251         * @return a stream tokenizer for reading MIF files
252         * @throws FileNotFoundException
253         * @throws UnsupportedEncodingException
254         */
255        public static StreamTokenizer getMIDTokenizer( File file, String charset )
256                                throws FileNotFoundException, UnsupportedEncodingException {
257            charset = getCharset( charset ).name();
258    
259            StreamTokenizer tok = new StreamTokenizer( new InputStreamReader( new FileInputStream( file ), charset ) );
260    
261            tok.resetSyntax();
262            tok.eolIsSignificant( true );
263            tok.lowerCaseMode( true );
264            tok.slashSlashComments( false );
265            tok.slashStarComments( false );
266            tok.wordChars( 'a', 'z' );
267            tok.wordChars( 'A', 'Z' );
268            tok.wordChars( '\u00a0', '\u00ff' );
269            tok.wordChars( '0', '9' );
270            wordChars( tok, '.', '-', '_' );
271            tok.quoteChar( '"' );
272            whitespaceChars( tok, ' ', '\n', '\r', '\f', '\t', '(', ')', ',' );
273    
274            return tok;
275        }
276    
277        /**
278         * @param mif
279         * @return a list of tokens, without the comma
280         * @throws IOException
281         */
282        public static LinkedList<String> parseCommaList( StreamTokenizer mif )
283                                throws IOException {
284            LinkedList<String> list = new LinkedList<String>();
285    
286            mif.ordinaryChar( ',' );
287            mif.nextToken();
288    
289            list.add( mif.sval );
290    
291            while ( mif.nextToken() == ',' ) {
292                mif.nextToken();
293                list.add( mif.sval );
294            }
295    
296            whitespaceChars( mif, ',' );
297    
298            return list;
299        }
300    
301        /**
302         * @throws IOException
303         */
304        public void parseFeatureType()
305                                throws IOException {
306            // don't parse it twice
307            if ( featureTypeParsed ) {
308                return;
309            }
310    
311            delimiter = '\t';
312    
313            mid.ordinaryChar( delimiter );
314    
315            LinkedList<PropertyType> propertyTypes = new LinkedList<PropertyType>();
316            propertyTypes.add( createGeometryPropertyType( new QualifiedName( "app", "geometry", APPNS ), null, 0, 1 ) );
317    
318            while ( mif.nextToken() != TT_EOF ) {
319                if ( mif.sval.equals( "version" ) ) {
320                    mif.nextToken();
321                    LOG.logDebug( "File version is " + mif.sval );
322                    if ( mif.sval.compareTo( "650" ) > 0 ) {
323                        LOG.logWarning( "Parsing an unknown version of " + mif.sval + "." );
324                    }
325                    continue;
326                }
327    
328                if ( mif.sval.equals( "charset" ) ) {
329                    mif.nextToken();
330                    String charset = mif.sval;
331                    // get new tokenizer, skip everything until the charset and continue
332                    mif = getMIFTokenizer( mifFile, charset );
333                    mid = getMIDTokenizer( midFile, charset );
334                    mid.ordinaryChar( delimiter );
335                    mif.nextToken();
336                    while ( !mif.sval.equals( "charset" ) ) {
337                        mif.nextToken();
338                    }
339                    mif.nextToken();
340                    continue;
341                }
342    
343                if ( mif.sval.equals( "delimiter" ) ) {
344                    mif.nextToken();
345                    delimiter = mif.sval.charAt( 0 );
346                    mid.ordinaryChar( delimiter );
347                    continue;
348                }
349    
350                if ( mif.sval.equals( "unique" ) ) {
351                    mif.nextToken();
352                    LOG.logWarning( "Ignoring unique directive." );
353                    continue;
354                }
355    
356                if ( mif.sval.equals( "index" ) ) {
357                    LOG.logWarning( "Ignoring but parsing index directive." );
358                    mif.nextToken();
359                    while ( true ) {
360                        try {
361                            parseInt( mif.sval );
362                            mif.nextToken();
363                        } catch ( NumberFormatException e ) {
364                            mif.pushBack();
365                            break;
366                        }
367                    }
368    
369                    continue;
370                }
371    
372                if ( mif.sval.equals( "coordsys" ) ) {
373                    LOG.logWarning( "Ignoring coordsys directive." );
374                    mif.nextToken();
375                    if ( mif.sval.equals( "window" ) ) {
376                        mif.nextToken(); // window_id
377                        continue;
378                    }
379    
380                    if ( mif.sval.equals( "table" ) ) {
381                        mif.nextToken(); // tablename
382                        continue;
383                    }
384    
385                    if ( mif.sval.equals( "layout" ) ) {
386                        mif.nextToken(); // Units
387                        mif.nextToken(); // paperunitname
388                        continue;
389                    }
390    
391                    if ( mif.sval.equals( "nonearth" ) ) {
392                        mif.nextToken(); // Units or Affine
393                        if ( mif.sval.equals( "affine" ) ) {
394                            mif.nextToken(); // Units
395                            mif.nextToken(); // unitname
396                            mif.nextToken(); // A
397                            mif.nextToken(); // B
398                            mif.nextToken(); // C
399                            mif.nextToken(); // D
400                            mif.nextToken(); // E
401                            mif.nextToken(); // F
402    
403                            mif.nextToken(); // Units
404                        }
405                        mif.nextToken(); // unitname
406                        mif.nextToken(); // Bounds
407                        mif.nextToken(); // minx
408                        mif.nextToken(); // miny
409                        mif.nextToken(); // maxx
410                        mif.nextToken(); // maxy
411                        continue;
412                    }
413    
414                    if ( mif.sval.equals( "earth" ) ) {
415                        mif.nextToken();
416                        if ( mif.sval.equals( "projection" ) ) {
417                            parseCommaList( mif );
418                        }
419    
420                        if ( !mif.sval.equals( "affine" ) && !mif.sval.equals( "bounds" ) ) {
421                            mif.pushBack();
422                        }
423    
424                        if ( mif.sval.equals( "affine" ) ) {
425                            mif.nextToken(); // Units
426                            mif.nextToken(); // unitname
427                            mif.nextToken(); // A
428                            mif.nextToken(); // B
429                            mif.nextToken(); // C
430                            mif.nextToken(); // D
431                            mif.nextToken(); // E
432                            mif.nextToken(); // F
433                        }
434    
435                        if ( mif.sval.equals( "bounds" ) ) {
436                            mif.nextToken(); // minx
437                            mif.nextToken(); // miny
438                            mif.nextToken(); // maxx
439                            mif.nextToken(); // maxy
440                        }
441    
442                        continue;
443                    }
444                }
445    
446                if ( mif.sval.equals( "transform" ) ) {
447                    mif.nextToken(); // four transformation parameters
448                    mif.nextToken();
449                    mif.nextToken();
450                    mif.nextToken();
451                }
452    
453                if ( mif.sval.equals( "columns" ) ) {
454                    mif.nextToken();
455                    int cnt = parseInt( mif.sval );
456                    for ( int i = 0; i < cnt; ++i ) {
457                        mif.lowerCaseMode( false );
458                        mif.nextToken();
459                        String name = mif.sval;
460                        mif.lowerCaseMode( true );
461                        mif.nextToken();
462                        String type = mif.sval;
463                        if ( type.equals( "integer" ) ) {
464                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), INTEGER,
465                                                                         true ) );
466                        }
467                        if ( type.equals( "smallint" ) ) {
468                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), INTEGER,
469                                                                         true ) );
470                        }
471                        if ( type.equals( "float" ) ) {
472                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), FLOAT,
473                                                                         true ) );
474                        }
475                        if ( type.equals( "date" ) ) {
476                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), DATE,
477                                                                         true ) );
478                        }
479                        if ( type.equals( "logical" ) ) {
480                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), BOOLEAN,
481                                                                         true ) );
482                        }
483                        if ( type.equals( "char" ) ) {
484                            mif.nextToken(); // size, is ignored (using varchar)
485                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), VARCHAR,
486                                                                         true ) );
487                        }
488                        if ( type.equals( "decimal" ) ) {
489                            // specifications are ignored, just using double
490                            mif.nextToken(); // width
491                            mif.nextToken(); // decimals
492                            propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", name, APPNS ), DOUBLE,
493                                                                         true ) );
494                        }
495                    }
496                    continue;
497                }
498    
499                if ( mif.sval.equals( "data" ) ) {
500                    propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", "styleid", APPNS ), VARCHAR,
501                                                                 true ) );
502                    propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", "text_geometry", APPNS ),
503                                                                 VARCHAR, true ) );
504                    propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", "text_minx", APPNS ), VARCHAR,
505                                                                 true ) );
506                    propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", "text_miny", APPNS ), VARCHAR,
507                                                                 true ) );
508                    propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", "text_maxx", APPNS ), VARCHAR,
509                                                                 true ) );
510                    propertyTypes.add( createSimplePropertyType( new QualifiedName( "app", "text_maxy", APPNS ), VARCHAR,
511                                                                 true ) );
512    
513                    featureType = createFeatureType( new QualifiedName( "app", "someName", APPNS ), false,
514                                                     propertyTypes.toArray( new PropertyType[propertyTypes.size()] ) );
515    
516                    featureTypeParsed = true;
517                    return;
518                }
519    
520                LOG.logWarning( "Spurious token: " + mif.sval );
521    
522            }
523        }
524    
525        /**
526         * @throws IOException
527         */
528        public void parseFeatures()
529                                throws IOException {
530            if ( featuresParsed ) {
531                return;
532            }
533    
534            parseFeatureType();
535    
536            mif.pushBack();
537            while ( mif.nextToken() != TT_EOF ) {
538    
539                if ( mif.sval.equals( "data" ) ) {
540                    mif.nextToken();
541    
542                    getFeatures();
543    
544                    featureCollection = createFeatureCollection( "parsedFeatureCollection",
545                                                                 features.toArray( new Feature[features.size()] ) );
546                    featuresParsed = true;
547                }
548    
549                LOG.logWarning( "Spurious token: " + mif.sval );
550    
551            }
552    
553        }
554    
555        private Pair<FeatureProperty, HashMap<String, HashMap<String, String>>> parseGeometry( CoordinateSystem crs,
556                                                                                               QualifiedName name )
557                                throws IOException {
558            if ( mif.sval.equals( "none" ) ) {
559                mif.nextToken();
560                LOG.logWarning( "A null geometry was found." );
561                return null;
562            }
563    
564            HashMap<String, HashMap<String, String>> map = new HashMap<String, HashMap<String, String>>();
565            Pair<FeatureProperty, HashMap<String, HashMap<String, String>>> pair;
566            pair = new Pair<FeatureProperty, HashMap<String, HashMap<String, String>>>();
567            pair.second = map;
568    
569            if ( mif.sval.equals( "point" ) ) {
570                Point p = parser.parsePoint();
571    
572                HashMap<String, String> symbol = styleParser.parseSymbol();
573                if ( symbol != null ) {
574                    map.put( "symbol", symbol );
575                }
576    
577                pair.first = createFeatureProperty( name, p );
578    
579                return pair;
580            }
581    
582            if ( mif.sval.equals( "multipoint" ) ) {
583                MultiPoint mp = parser.parseMultipoint();
584    
585                HashMap<String, String> symbol = styleParser.parseSymbol();
586                if ( symbol != null ) {
587                    map.put( "symbol", symbol );
588                }
589    
590                pair.first = createFeatureProperty( name, mp );
591    
592                return pair;
593            }
594    
595            if ( mif.sval.equals( "line" ) ) {
596                Curve l = parser.parseLine();
597    
598                HashMap<String, String> pen = styleParser.parsePen();
599                if ( pen != null ) {
600                    map.put( "pen", pen );
601                }
602    
603                pair.first = createFeatureProperty( name, l );
604    
605                return pair;
606            }
607    
608            if ( mif.sval.equals( "pline" ) ) {
609                Curve c = parser.parsePLine();
610    
611                HashMap<String, String> pen = styleParser.parsePen();
612                if ( pen != null ) {
613                    map.put( "pen", pen );
614                }
615    
616                if ( mif.sval != null && mif.sval.equals( "smooth" ) ) {
617                    LOG.logWarning( "Smoothing is not supported, since it uses proprietary ad-hoc algorithms." );
618                    mif.nextToken();
619                }
620    
621                pair.first = createFeatureProperty( name, c );
622    
623                return pair;
624            }
625    
626            if ( mif.sval.equals( "region" ) ) {
627                MultiSurface ms = parser.parseRegion();
628    
629                HashMap<String, String> pen = styleParser.parsePen();
630                if ( pen != null ) {
631                    map.put( "pen", pen );
632                }
633    
634                HashMap<String, String> brush = styleParser.parseBrush();
635                if ( brush != null ) {
636                    map.put( "brush", brush );
637                }
638    
639                if ( mif.sval != null && mif.sval.equals( "center" ) ) {
640                    LOG.logWarning( "Custom centroid settings are not supported." );
641                    mif.nextToken();
642                    mif.nextToken();
643                    mif.nextToken();
644                }
645    
646                pair.first = createFeatureProperty( name, ms );
647    
648                return pair;
649            }
650    
651            if ( mif.sval.equals( "arc" ) ) {
652                parser.parseArc();
653    
654                HashMap<String, String> pen = styleParser.parsePen();
655                if ( pen != null ) {
656                    map.put( "pen", pen );
657                }
658    
659                return null;
660            }
661    
662            if ( mif.sval.equals( "roundrect" ) ) {
663                parser.parseRoundRect();
664    
665                HashMap<String, String> pen = styleParser.parsePen();
666                if ( pen != null ) {
667                    map.put( "pen", pen );
668                }
669    
670                HashMap<String, String> brush = styleParser.parseBrush();
671                if ( brush != null ) {
672                    map.put( "brush", brush );
673                }
674    
675                return null;
676            }
677    
678            if ( mif.sval.equals( "ellipse" ) ) {
679                parser.parseEllipse();
680    
681                HashMap<String, String> pen = styleParser.parsePen();
682                if ( pen != null ) {
683                    map.put( "pen", pen );
684                }
685    
686                HashMap<String, String> brush = styleParser.parseBrush();
687                if ( brush != null ) {
688                    map.put( "brush", brush );
689                }
690    
691                return null;
692            }
693    
694            if ( mif.sval.equals( "rect" ) ) {
695                Surface s = parser.parseRect();
696    
697                HashMap<String, String> pen = styleParser.parsePen();
698                if ( pen != null ) {
699                    map.put( "pen", pen );
700                }
701    
702                HashMap<String, String> brush = styleParser.parseBrush();
703                if ( brush != null ) {
704                    map.put( "brush", brush );
705                }
706    
707                pair.first = createFeatureProperty( name, s );
708    
709                return pair;
710            }
711    
712            if ( mif.sval.equals( "collection" ) ) {
713                LOG.logDebug( "Parsing collection..." );
714                LOG.logWarning( "Collections are not understood and will be ignored. This will break the parsing!" );
715                mif.nextToken();
716                return null;
717            }
718    
719            if ( mif.sval.equals( "text" ) ) {
720                LOG.logDebug( "Parsing text..." );
721                LOG.logWarning( "Text geometries will be parsed as points." );
722    
723                mif.nextToken();
724                String text = mif.sval;
725                mif.nextToken();
726    
727                double x1 = parseDouble( mif.sval );
728                mif.nextToken();
729                double y1 = parseDouble( mif.sval );
730                mif.nextToken();
731    
732                double x2 = parseDouble( mif.sval );
733                mif.nextToken();
734                double y2 = parseDouble( mif.sval );
735                mif.nextToken();
736    
737                HashMap<String, String> style = styleParser.parseText();
738                if ( style == null ) {
739                    style = new HashMap<String, String>();
740                }
741    
742                style.put( "minx", "" + x1 );
743                style.put( "miny", "" + y1 );
744                style.put( "maxx", "" + x2 );
745                style.put( "maxy", "" + y2 );
746                style.put( "text", text );
747                map.put( "text", style );
748    
749                Point p = createPoint( x1, y1, crs );
750    
751                pair.first = createFeatureProperty( name, p );
752    
753                return pair;
754            }
755    
756            LOG.logWarning( "Unknown construct: " + mif.sval );
757            mif.nextToken();
758    
759            return null;
760        }
761    
762        // mif stream is kept at beginning of next geometry
763        private void getFeatures()
764                                throws IOException {
765            CoordinateSystem crs = null; // TODO
766    
767            final QualifiedName styleName = new QualifiedName( "app", "styleid", APPNS );
768            final QualifiedName textName = new QualifiedName( "app", "text_geometry", APPNS );
769            final QualifiedName minxName = new QualifiedName( "app", "text_minx", APPNS );
770            final QualifiedName minyName = new QualifiedName( "app", "text_miny", APPNS );
771            final QualifiedName maxxName = new QualifiedName( "app", "text_maxx", APPNS );
772            final QualifiedName maxyName = new QualifiedName( "app", "text_maxy", APPNS );
773            int styleNum = 0;
774    
775            int id = 0;
776    
777            features = new LinkedList<Feature>();
778    
779            DateFormat df = new SimpleDateFormat( "yyyymmdd" );
780    
781            parser = new MIFGeometryParser( mif, crs );
782            styleParser = new MIFStyleParser( mif, this.mifFile.getParentFile() );
783    
784            styles = new HashMap<String, HashSet<HashMap<String, String>>>();
785    
786            while ( mif.ttype != TT_EOF ) {
787                LinkedList<FeatureProperty> properties = new LinkedList<FeatureProperty>();
788    
789                PropertyType[] ps = featureType.getProperties();
790                // skip the styleid, text, and minx, miny, maxx, maxy for the text
791                for ( int i = 0; i < ps.length - 6; ++i ) {
792                    String field = null;
793                    if ( i != 0 ) { // 0 is the geometry
794                        StringBuffer sb = new StringBuffer();
795                        while ( mid.nextToken() != delimiter && mid.ttype != TT_EOL && mid.ttype != TT_EOF ) {
796                            sb.append( mid.sval );
797                        }
798                        field = sb.toString();
799                    }
800    
801                    switch ( ps[i].getType() ) {
802                    case GEOMETRY: {
803                        Pair<FeatureProperty, HashMap<String, HashMap<String, String>>> pair;
804                        pair = parseGeometry( crs, ps[i].getName() );
805                        if ( pair != null && pair.first != null ) {
806                            properties.add( pair.first );
807    
808                            String usedStyle = null;
809    
810                            // update styles map
811                            for ( String key : pair.second.keySet() ) {
812                                HashMap<String, String> ss;
813                                ss = pair.second.get( key );
814    
815                                if ( ss != null ) {
816                                    if ( key.equals( "text" ) ) {
817                                        String minx = ss.remove( "minx" );
818                                        String miny = ss.remove( "miny" );
819                                        String maxx = ss.remove( "maxx" );
820                                        String maxy = ss.remove( "maxy" );
821                                        String text = ss.remove( "text" );
822                                        properties.add( createFeatureProperty( textName, text ) );
823                                        properties.add( createFeatureProperty( minxName, minx ) );
824                                        properties.add( createFeatureProperty( minyName, miny ) );
825                                        properties.add( createFeatureProperty( maxxName, maxx ) );
826                                        properties.add( createFeatureProperty( maxyName, maxy ) );
827                                    }
828    
829                                    if ( styles.get( key ) == null ) {
830                                        HashSet<HashMap<String, String>> set = new HashSet<HashMap<String, String>>();
831                                        set.add( ss );
832                                        styles.put( key, set );
833                                        ss.put( "styleid", "" + styleNum );
834                                        if ( usedStyle == null ) {
835                                            usedStyle = "" + styleNum++;
836                                        } else {
837                                            usedStyle = usedStyle + "_" + styleNum++;
838                                        }
839                                    } else {
840                                        HashSet<HashMap<String, String>> set = styles.get( key );
841                                        if ( !set.contains( ss ) ) {
842                                            // check for same style, but different ID
843                                            boolean found = false;
844                                            String styleid = null;
845                                            for ( HashMap<String, String> m : set ) {
846                                                HashMap<String, String> woid = new HashMap<String, String>( m );
847                                                woid.remove( "styleid" );
848                                                if ( woid.equals( ss ) ) {
849                                                    found = true;
850                                                    styleid = m.get( "styleid" );
851                                                    break;
852                                                }
853                                            }
854                                            if ( !found ) {
855                                                set.add( ss );
856                                                ss.put( "styleid", "" + styleNum );
857                                                if ( usedStyle == null ) {
858                                                    usedStyle = "" + styleNum++;
859                                                } else {
860                                                    usedStyle = usedStyle + "_" + styleNum++;
861                                                }
862                                            } else {
863                                                if ( usedStyle == null ) {
864                                                    usedStyle = "" + styleid;
865                                                } else {
866                                                    usedStyle = usedStyle + "_" + styleid;
867                                                }
868                                            }
869                                        }
870                                    }
871                                }
872                            }
873    
874                            if ( usedStyle != null ) {
875                                properties.add( createFeatureProperty( styleName, usedStyle ) );
876                            }
877                        }
878                        continue;
879                    }
880                    case INTEGER: {
881                        Integer val = Integer.valueOf( field );
882                        properties.add( createFeatureProperty( ps[i].getName(), val ) );
883                        continue;
884                    }
885                    case FLOAT: {
886                        Float val = Float.valueOf( field );
887                        properties.add( createFeatureProperty( ps[i].getName(), val ) );
888                        continue;
889                    }
890                    case DOUBLE: {
891                        Double val = Double.valueOf( field );
892                        properties.add( createFeatureProperty( ps[i].getName(), val ) );
893                        continue;
894                    }
895                    case DATE: {
896                        Date val = null;
897                        try {
898                            val = df.parse( field );
899                            properties.add( createFeatureProperty( ps[i].getName(), val ) );
900                        } catch ( ParseException e ) {
901                            // ignore it
902                            LOG.logWarning( "A date value could not be parsed." );
903                        }
904                        continue;
905                    }
906                    case BOOLEAN: {
907                        Boolean val = Boolean.valueOf( field );
908                        properties.add( createFeatureProperty( ps[i].getName(), val ) );
909                        continue;
910                    }
911                    case VARCHAR: {
912                        String val = field;
913                        properties.add( createFeatureProperty( ps[i].getName(), val ) );
914                        continue;
915                    }
916                    }
917                }
918    
919                features.add( createFeature( "" + id++, featureType, properties ) );
920    
921            }
922    
923        }
924    
925        /**
926         * @return the feature collection (null if it has not been parsed yet)
927         */
928        public FeatureCollection getFeatureCollection() {
929            return featureCollection;
930        }
931    
932        /**
933         * @return the styles (null, if they've not been parsed yet)
934         */
935        public HashMap<String, HashSet<HashMap<String, String>>> getStyles() {
936            return styles;
937        }
938    
939        /**
940         * @return the feature type, or null, if it has not been parsed yet
941         */
942        public FeatureType getFeatureType() {
943            return featureType;
944        }
945    
946        /**
947         * @return a list of geometry errors
948         */
949        public LinkedList<String> getErrors() {
950            return parser.errors;
951        }
952    
953    }