001    //$$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/tools/shape/AVL2SLD.java $$
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2008 by:
005     Department of Geography, University of Bonn
006     http://www.giub.uni-bonn.de/deegree/
007     lat/lon GmbH
008     http://www.lat-lon.de
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014    
015     This library is distributed in the hope that it will be useful,
016     but WITHOUT ANY WARRANTY; without even the implied warranty of
017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018     Lesser General Public License for more details.
019    
020     You should have received a copy of the GNU Lesser General Public
021     License along with this library; if not, write to the Free Software
022     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
023    
024     Contact:
025    
026     Andreas Poth
027     lat/lon GmbH
028     Aennchenstraße 19
029     53177 Bonn
030     Germany
031     E-Mail: poth@lat-lon.de
032    
033     Prof. Dr. Klaus Greve
034     Department of Geography
035     University of Bonn
036     Meckenheimer Allee 166
037     53115 Bonn
038     Germany
039     E-Mail: greve@giub.uni-bonn.de
040    
041     ---------------------------------------------------------------------------*/
042    
043    package org.deegree.tools.shape;
044    
045    import java.awt.Color;
046    import java.awt.image.BufferedImage;
047    import java.io.File;
048    import java.io.FileOutputStream;
049    import java.io.FileReader;
050    import java.io.FileWriter;
051    import java.io.FilenameFilter;
052    import java.io.IOException;
053    import java.io.Reader;
054    import java.util.ArrayList;
055    import java.util.HashMap;
056    import java.util.List;
057    import java.util.Map;
058    import java.util.regex.Matcher;
059    import java.util.regex.Pattern;
060    
061    import org.deegree.datatypes.QualifiedName;
062    import org.deegree.framework.util.ImageUtils;
063    import org.deegree.framework.util.StringTools;
064    import org.deegree.framework.xml.Marshallable;
065    import org.deegree.graphics.sld.AbstractLayer;
066    import org.deegree.graphics.sld.AbstractStyle;
067    import org.deegree.graphics.sld.ExternalGraphic;
068    import org.deegree.graphics.sld.FeatureTypeStyle;
069    import org.deegree.graphics.sld.Fill;
070    import org.deegree.graphics.sld.Font;
071    import org.deegree.graphics.sld.Graphic;
072    import org.deegree.graphics.sld.GraphicFill;
073    import org.deegree.graphics.sld.LabelPlacement;
074    import org.deegree.graphics.sld.LineSymbolizer;
075    import org.deegree.graphics.sld.Mark;
076    import org.deegree.graphics.sld.NamedLayer;
077    import org.deegree.graphics.sld.PointPlacement;
078    import org.deegree.graphics.sld.PointSymbolizer;
079    import org.deegree.graphics.sld.PolygonSymbolizer;
080    import org.deegree.graphics.sld.Rule;
081    import org.deegree.graphics.sld.Stroke;
082    import org.deegree.graphics.sld.StyleFactory;
083    import org.deegree.graphics.sld.StyledLayerDescriptor;
084    import org.deegree.graphics.sld.Symbolizer;
085    import org.deegree.graphics.sld.TextSymbolizer;
086    import org.deegree.io.shpapi.MainFile;
087    import org.deegree.io.shpapi.ShapeConst;
088    import org.deegree.model.filterencoding.ComplexFilter;
089    import org.deegree.model.filterencoding.Filter;
090    import org.deegree.model.filterencoding.FilterConstructionException;
091    import org.deegree.model.filterencoding.Literal;
092    import org.deegree.model.filterencoding.LogicalOperation;
093    import org.deegree.model.filterencoding.Operation;
094    import org.deegree.model.filterencoding.OperationDefines;
095    import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
096    import org.deegree.model.filterencoding.PropertyName;
097    
098    /**
099     * <p>
100     * This class converts ESRI *.avl files to OGC SLD documents. The current version of this tool isn't
101     * able to convert each and every construction that is possible with an *.avl file. But most of the
102     * common expressions will be mapped.
103     * </p>
104     * <p>
105     * Because SLD (version 1.0.0) does not know inline bitmap or fill pattern definition directly
106     * (maybe it is possible using some SVG tags) all polygon fill patterns must be converted to images
107     * that are written to the file system and referenced as external graphic by the created SLD style.
108     * The similar is true for symbol definitions. SLD just 'knowns' a few predefined symbols that are
109     * not able to capture all symbols known by ArcView. In this context deegree also will extend the
110     * well known symbol by using ASCII codes to references symbols defined in an available font. This
111     * will enable deegree to transform the ArcView option defining a symbol by using an ACSII character
112     * directly. At least even if the synthax for this is SLD compliant most SLD implementation probably
113     * won't be able to evaluate this.
114     * </p>
115     * 
116     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
117     * @author last edited by: $Author: apoth $
118     * 
119     * @version $Revision: 9346 $, $Date: 2007-12-27 17:39:07 +0100 (Do, 27 Dez 2007) $
120     */
121    public class AVL2SLD {
122    
123        private String fileRootName = null;
124    
125        private String targetDir = null;
126    
127        private Map blocks = new HashMap();
128    
129        private static AVLPointSymbolCodeList cl = null;
130    
131        /**
132         * @param fileRootName
133         * @param targetDir
134         */
135        public AVL2SLD( String fileRootName, String targetDir ) {
136            this.fileRootName = fileRootName;
137            this.targetDir = targetDir;
138            if ( !this.targetDir.endsWith( "/" ) ) {
139                this.targetDir = this.targetDir + "/";
140            }
141        }
142    
143        /**
144         * reads the avl file assigned to a class instance
145         * 
146         * @throws IOException
147         */
148        public void read()
149                                throws IOException {
150            Reader reader = new FileReader( fileRootName + ".avl" );
151            StringBuffer sb = new StringBuffer( 50000 );
152            int c = 0;
153            while ( ( c = reader.read() ) > -1 ) {
154                if ( c == 9 )
155                    c = ' ';
156                // if ( c != 10 && c != 13) {
157                sb.append( (char) c );
158                // }
159            }
160            reader.close();
161    
162            // create a entry in 'blocks' for each '(' ... ')'
163            // enclosed section
164            String[] s1 = splitavl( sb.toString() );
165    
166            for ( int i = 1; i < s1.length; i++ ) {
167                // write each KVP of section to a map and store it
168                // in the blocks map.
169                // the Class, Pattern and he Child key will be treated
170                // as array because it isn't unique
171                int pos = s1[i].indexOf( ' ' );
172                if ( pos < 0 )
173                    continue;
174                String section = s1[i].substring( 0, pos ).trim();
175                String[] s2 = StringTools.toArray( s1[i].substring( pos, s1[i].length() ), ":\n", false );
176                Map<String,Object> block = new HashMap<String,Object>();
177                for ( int j = 0; j < s2.length; j = j + 2 ) {
178                    if ( s2[j].trim().equals( "Class" ) ) {
179                        List<String> list = (List<String>)block.get( "Class" );
180                        if ( list == null ) {
181                            list = new ArrayList<String>();
182                        }
183                        list.add( s2[j + 1] );
184                        block.put( s2[j], list );
185                    } else if ( s2[j].trim().equals( "Child" ) ) {
186                        List<String> list = (List<String>)block.get( "Child" );
187                        if ( list == null ) {
188                            list = new ArrayList<String>();
189                        }
190                        list.add( s2[j + 1] );
191                        block.put( s2[j], list );
192                    } else if ( s2[j].trim().equals( "Pattern" ) ) {
193                        List<String> list = (List<String>)block.get( "Pattern" );
194                        if ( list == null ) {
195                            list = new ArrayList<String>();
196                        }
197                        list.add( s2[j + 1] );
198                        block.put( s2[j], list );
199                    } else if ( s2[j].trim().equals( "Bits" ) ) {
200                        List<String> list = (List<String>)block.get( "Bits" );
201                        if ( list == null ) {
202                            list = new ArrayList<String>();
203                        }
204                        list.add( s2[j + 1] );
205                        block.put( s2[j], list );
206                    } else {
207                        block.put( s2[j], s2[j + 1].trim() );
208                    }
209                }
210                blocks.put( section, block );
211            }
212    
213        }
214    
215        /**
216         * returns a <tt>Style</tt>. For each class/child contained in a avl file one <tt>Rule</tt>
217         * will be created
218         * 
219         * @return a <tt>Style</tt>. For each class/child contained in a avl file one <tt>Rule</tt>
220         *         will be created
221         * @throws IOException
222         * @throws Exception
223         */
224        public AbstractStyle getStyle()
225                                throws IOException, Exception {
226            Map odb = (Map) blocks.get( "ODB.1" );
227            String roots = (String) odb.get( "Roots" );
228            Map legend = (Map) blocks.get( "Legend." + roots );
229            String filterCol = null;
230            if ( legend.get( "FieldNames" ) != null ) {
231                Map block = (Map) blocks.get( "AVStr." + legend.get( "FieldNames" ) );
232                filterCol = (String) block.get( "S" );
233                filterCol = StringTools.validateString( filterCol, "\"" ).toUpperCase();
234            }
235    
236            int geoType = getGeometryType();
237    
238            AbstractStyle style = null;
239            switch ( geoType ) {
240            case ShapeConst.SHAPE_TYPE_POINT:
241                style = createPointStyle( legend, filterCol );
242                break;
243            case ShapeConst.SHAPE_TYPE_POLYLINE:
244                style = createLinesStyle( legend, filterCol );
245                break;
246            case ShapeConst.SHAPE_TYPE_POLYGON:
247                style = createPolygonStyle( legend, filterCol );
248                break;
249            case ShapeConst.SHAPE_TYPE_MULTIPOINT:
250                style = createPointStyle( legend, filterCol );
251                break;
252            default:
253                throw new Exception( "unknown geometry type: " + geoType );
254            }
255            return style;
256        }
257    
258        /**
259         * creates a <tt>StyledLayerDescriptor</tt> from the avl file assigned to the instace of a
260         * <tt>AVLReader</tt>. The returned instance of a <tt>StyledLayerDescriptor</tt> just
261         * contains one style that may containes several <tt>Rule</tt>s
262         * 
263         * @return a <tt>StyledLayerDescriptor</tt> created from the avl file assigned to the instace
264         *         of a <tt>AVLReader</tt>. The returned instance of a <tt>StyledLayerDescriptor</tt>
265         *         just contains one style that may containes several <tt>Rule</tt>s
266         * @throws IOException
267         * @throws Exception
268         */
269        public StyledLayerDescriptor getStyledLayerDescriptor()
270                                throws IOException, Exception {
271            AbstractStyle style = getStyle();
272            String[] t = StringTools.toArray( fileRootName, "/", false );
273            String name = "default:" + t[t.length - 1];
274            AbstractLayer layer = new NamedLayer( name, null, new AbstractStyle[] { style } );
275            return new StyledLayerDescriptor( new AbstractLayer[] { layer }, "1.0.0" );
276        }
277    
278        /**
279         * parse a string and return array of blocks between braces "(" and ")". It accounts for braces
280         * in quoted strings.
281         * 
282         * @param s
283         *            string to parse
284         * @return
285         */
286        public static String[] splitavl( String s ) {
287            if ( s == null || s.equals( "" ) ) {
288                return new String[0];
289            }
290    
291            Pattern pat = Pattern.compile( "\\(([^)\"]|\"[^\"]*\")*\\)" );
292            Matcher mat = pat.matcher( s );
293            int prevend = 0;
294            ArrayList<String> vec = new ArrayList<String>();
295            while ( mat.find() ) {
296                int start = mat.start();
297                int end = mat.end();
298                if ( prevend < start - 1 ) {
299                    String str = s.substring( prevend, start ).trim();
300                    if ( str.length() > 0 ) {
301                        vec.add( str );
302                    }
303                }
304                String str = s.substring( start + 1, end - 1 ).trim();
305                if ( str.length() > 0 ) {
306                    vec.add( str );
307                }
308                prevend = end;
309            }
310            if ( prevend < s.length() - 1 ) {
311                String str = s.substring( prevend ).trim();
312                if ( str.length() > 0 ) {
313                    vec.add( str );
314                }
315            }
316    
317            // no value selected
318            if ( vec.size() == 0 ) {
319                return new String[0];
320            }
321    
322            return vec.toArray( new String[vec.size()] );
323        }
324    
325        private int getGeometryType()
326                                throws IOException {
327            MainFile mf = new MainFile( fileRootName );
328            int type = mf.getShapeTypeByRecNo( 1 );
329            mf.close();
330            return type;
331        }
332    
333        /**
334         * creates a <tt>Style</tt>. For each class/child contained in a avl file one <tt>Rule</tt>
335         * will be created
336         * 
337         * @param legend
338         * @param filterCol
339         * @return a <tt>Style</tt>.
340         */
341        private AbstractStyle[] createPointStyles( Map legend, String filterCol ) {
342            AbstractStyle[] styles = null;
343            return styles;
344        }
345    
346        /**
347         * creates a <tt>Style</tt>. For each class/child contained in a avl file one <tt>Rule</tt>
348         * will be created
349         * 
350         * @param legend
351         * @param filterCol
352         * @return a <tt>Style</tt>.
353         */
354        private AbstractStyle createPointStyle( Map legend, String filterCol )
355                                throws FilterConstructionException {
356            try {
357                cl = new AVLPointSymbolCodeList();
358            } catch ( Exception e ) {
359                e.printStackTrace();
360            }
361            String tmp = (String) legend.get( "Symbols" );
362            Map block = (Map) blocks.get( "SymList." + tmp );
363            List classes = (List) legend.get( "Class" );
364            List children = (List) block.get( "Child" );
365    
366            List<Rule> list = new ArrayList<Rule>( classes.size() );
367            for ( int i = 0; i < classes.size(); i++ ) {
368                String clNo = (String) classes.get( i );
369                Map clss = (Map) blocks.get( "LClass." + clNo );
370                String childNo = (String) children.get( i );
371                Map child = (Map) blocks.get( "CMkSym." + childNo );
372                Rule rule = null;
373                if ( child == null ) {
374                    child = (Map) blocks.remove( "BMkSym." + childNo );
375                    rule = createSimplePointRule( clss, child, filterCol );
376                } else {
377                    rule = createComplexPointRule( clss, child, filterCol );
378                }
379                if ( rule != null ) {
380                    list.add( rule );
381                }
382            }
383            Rule[] rules = list.toArray( new Rule[list.size()] );
384            FeatureTypeStyle fts = StyleFactory.createFeatureTypeStyle( rules );
385    
386            String[] t = StringTools.toArray( fileRootName, "/", false );
387            String name = "default:" + t[t.length - 1];
388            return StyleFactory.createStyle( name, null, null, fts );
389        }
390    
391        /**
392         * creates a Style for a line symbol
393         * 
394         * @param clss
395         * @param child
396         * @return a Style for a line symbol
397         */
398        private Rule createSimplePointRule( Map clss, Map child, String filterCol ) {
399    
400            if ( clss.get( "IsNoData" ) != null ) {
401                return null;
402            }
403    
404            String label = (String) clss.get( "Label" );
405            label = StringTools.validateString( label, "\"" );
406            Filter filter = createFilter( clss, filterCol );
407            // get foreground color
408            String clrIdx = (String) child.get( "Color" );
409            Map color = (Map) blocks.get( "TClr." + clrIdx );
410            double fgOpacity = 1f;
411            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
412                fgOpacity = 0f;
413            }
414            Color fgColor = createColor( color );
415            if ( fgColor == null ) {
416                fgColor = Color.BLACK;
417            }
418            // get background color (what ever this means)
419            clrIdx = (String) child.get( "BgColor" );
420            color = (Map) blocks.get( "TClr." + clrIdx );
421            double bgOpacity = 1f;
422            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
423                bgOpacity = 0f;
424            }
425            Color bgColor = createColor( color );
426            if ( bgColor == null ) {
427                bgColor = Color.BLACK;
428            }
429    
430            double size = Double.parseDouble( (String) child.get( "Size" ) );
431            double rotation = Double.parseDouble( (String) child.get( "Angle" ) );
432    
433            // creation of a font from a avl file is a triky thing because
434            // esri uses their own font names and codes
435            // --> don' know if this works
436            String fntIdx = (String) child.get( "Font" );
437            Map fntMap = (Map) blocks.get( "NFont." + fntIdx );
438            Font font = createFont( fntMap );
439            PointPlacement pp = StyleFactory.createPointPlacement();
440            LabelPlacement labelPlacement = StyleFactory.createLabelPlacement( pp );
441            TextSymbolizer textSym = StyleFactory.createTextSymbolizer( fgColor, font, filterCol,
442                                                                        labelPlacement );
443    
444            String patternIdx = (String) ( (ArrayList) child.get( "Pattern" ) ).get( 0 );
445            String symbol = cl.getSymbol( patternIdx );
446    
447            // create a Mark with a stroke and fill to controll both
448            // opacities
449            Stroke stroke = StyleFactory.createStroke( fgColor, 1, fgOpacity, null, "mitre", "butt" );
450            Fill fill = StyleFactory.createFill( bgColor, bgOpacity );
451            Mark mark = StyleFactory.createMark( symbol, fill, stroke );
452    
453            Graphic graphic = StyleFactory.createGraphic( null, mark, 1, size, rotation );
454            PointSymbolizer pointSym = StyleFactory.createPointSymbolizer( graphic );
455    
456            return StyleFactory.createRule( new Symbolizer[] { pointSym, textSym }, label, label, "",
457                                            null, filter, false, 0, 9E99 );
458        }
459    
460        /**
461         * 
462         * @param clss
463         * @param child
464         * @param filterCol
465         * @return
466         * @throws FilterConstructionException
467         */
468        private Rule createComplexPointRule( Map clss, Map child, String filterCol )
469                                throws FilterConstructionException {
470    
471            if ( clss.get( "IsNoData" ) != null ) {
472                return null;
473            }
474    
475            String tmp = (String) child.get( "Symbols" );
476            Map block = (Map) blocks.get( "SymList." + tmp );
477            List children = (List) block.get( "Child" );
478    
479            List<Symbolizer> smbls = new ArrayList<Symbolizer>();
480            for ( int i = 0; i < children.size(); i++ ) {
481                String childNo = (String) children.get( i );
482                Map child_ = (Map) blocks.get( "CMkSym." + childNo );
483                Rule rule = null;
484                if ( child_ == null ) {
485                    child = (Map) blocks.remove( "BMkSym." + childNo );
486                    rule = createSimplePointRule( clss, child, filterCol );
487                } else {
488                    rule = createComplexPointRule( clss, child, filterCol );
489                }
490                Symbolizer[] sym = rule.getSymbolizers();
491                for ( int j = 0; j < sym.length; j++ ) {
492                    smbls.add( sym[j] );
493                }
494            }
495            Symbolizer[] sym = new Symbolizer[smbls.size()];
496            sym = smbls.toArray( sym );
497    
498            String label = (String) clss.get( "Label" );
499            label = StringTools.validateString( label, "\"" );
500            Filter filter = createFilter( clss, filterCol );
501    
502            return StyleFactory.createRule( sym, label, label, "", null, filter, false, 0, 9E99 );
503        }
504    
505        private Font createFont( Map fntMap ) {
506            String idx = (String) fntMap.get( "Family" );
507            String family = (String) ( (Map) blocks.get( "AVStr." + idx ) ).get( "S" );
508            idx = (String) fntMap.get( "Name" );
509            String name = (String) ( (Map) blocks.get( "AVStr." + idx ) ).get( "S" );
510            idx = (String) fntMap.get( "Style" );
511            String style = (String) ( (Map) blocks.get( "AVStr." + idx ) ).get( "S" );
512            String weight = (String) fntMap.get( "Weight" );
513            String wideness = (String) fntMap.get( "Wideness" );
514    
515            boolean italic = style.equals( "Italic" );
516            boolean bold = Integer.parseInt( weight ) > 1;
517    
518            return StyleFactory.createFont( family, italic, bold, 12 );
519        }
520    
521        /**
522         * creates a <tt>Style</tt>. For each class/child contained in a avl file one <tt>Rule</tt>
523         * will be created
524         * 
525         * @param legend
526         * @param filterCol
527         * @return a <tt>Style</tt>.
528         */
529        private AbstractStyle createLinesStyle( Map legend, String filterCol ) {
530            String tmp = (String) legend.get( "Symbols" );
531            Map block = (Map) blocks.get( "SymList." + tmp );
532            List classes = (List) legend.get( "Class" );
533            List children = (List) block.get( "Child" );
534            List<Rule> list = new ArrayList<Rule>( classes.size() );
535            for ( int i = 0; i < classes.size(); i++ ) {
536                String clNo = (String) classes.get( i );
537                Map clss = (Map) blocks.get( "LClass." + clNo );
538                String childNo = (String) children.get( i );
539                Map child = (Map) blocks.get( "BLnSym." + childNo );
540    
541                if ( child == null ) {
542                    // won't be treated correctly because we can't transform
543                    // lines with sambols at the moment
544                    child = (Map) blocks.get( "CLnSym." + childNo );
545                }
546                Rule rule = createLineRule( clss, child, filterCol );
547                if ( rule != null ) {
548                    list.add( rule );
549                }
550            }
551            Rule[] rules = list.toArray( new Rule[list.size()] );
552            FeatureTypeStyle fts = StyleFactory.createFeatureTypeStyle( rules );
553    
554            String[] t = StringTools.toArray( fileRootName, "/", false );
555            String name = "default:" + t[t.length - 1];
556            return StyleFactory.createStyle( name, null, null, fts );
557        }
558    
559        /**
560         * creates a Style for a line symbol
561         * 
562         * @param clss
563         * @param child
564         * @return a Style for a line symbol
565         */
566        private Rule createLineRule( Map clss, Map child, String filterCol ){
567    
568            if ( clss.get( "IsNoData" ) != null ) {
569                return null;
570            }
571    
572            String label = (String) clss.get( "Label" );
573            label = StringTools.validateString( label, "\"" );
574            Filter filter = createFilter( clss, filterCol );
575    
576            String clrIdx = (String) child.get( "Color" );
577            Map color = (Map) blocks.get( "TClr." + clrIdx );
578            double opacity = 1f;
579            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) && color.size() > 0 ) {
580                opacity = 0f;
581            }
582            Color colour = createColor( color );
583            if ( colour == null ) {
584                colour = Color.BLACK;
585            }
586    
587            double width = 1.0; // default Width
588            if ( child.get( "Width" ) != null ) {
589                width = Double.parseDouble( (String) child.get( "Width" ) ) + width;
590            }
591            // double width = Double.parseDouble( (String)child.get("Width") ) + 1;
592            List pl = (List) child.get( "Pattern" );
593    
594            if ( child.get( "Pattern" ) == null ) { // create a default pattern List if
595                // it is null
596                pl = new ArrayList<String>();
597                for ( int i = 0; i < 16; i++ ) { // Fill the default List with
598                    // default values "0.00000000000000"
599                    pl.add( "0.00000000000000" );
600                }
601            }
602    
603            // List pl = (List)child.get("Pattern");
604            float[] dashArray = createDashArray( pl );
605            Stroke stroke = StyleFactory.createStroke( colour, width, opacity, dashArray, "mitre",
606                                                       "butt" );
607            LineSymbolizer lineSym = StyleFactory.createLineSymbolizer( stroke );
608    
609            return StyleFactory.createRule( new Symbolizer[] { lineSym }, label, label, "", null,
610                                            filter, false, 0, 9E99 );
611        }
612    
613        /**
614         * creates a <tt>Style</tt>. For each class/child contained in a avl file one <tt>Rule</tt>
615         * will be created
616         * 
617         * @param legend
618         * @param filterCol
619         * @return a <tt>Style</tt>.
620         */
621        private AbstractStyle createPolygonStyle( Map legend, String filterCol )
622                                throws Exception {
623            String tmp = (String) legend.get( "Symbols" );
624            Map block = (Map) blocks.get( "SymList." + tmp );
625            List classes = (List) legend.get( "Class" );
626            List children = (List) block.get( "Child" );
627            List<Rule> list = new ArrayList<Rule>( classes.size() );
628            for ( int i = 0; i < classes.size(); i++ ) {
629                String clNo = (String) classes.get( i );
630                Map clss = (Map) blocks.get( "LClass." + clNo );
631                String childNo = (String) children.get( i );
632                Map child = (Map) blocks.get( "BShSym." + childNo );
633                Rule rule = null;
634                if ( child == null ) {
635                    // VShSym is a vector polygon fill
636                    child = (Map) blocks.get( "VShSym." + childNo );
637                    rule = createPolygonVecRule( clss, child, filterCol );
638                } else {
639                    rule = createPolygonBMPRule( clss, child, filterCol );
640                }
641                if ( rule != null ) {
642                    list.add( rule );
643                }
644                // TODO
645                // write special method for vector polygon fill
646            }
647            Rule[] rules = list.toArray( new Rule[list.size()] );
648            FeatureTypeStyle fts = StyleFactory.createFeatureTypeStyle( rules );
649    
650            String[] t = StringTools.toArray( fileRootName, "/", false );
651            String name = "default:" + t[t.length - 1];
652            return StyleFactory.createStyle( name, null, null, fts );
653        }
654    
655        /**
656         * creates a Style for a line symbol
657         * 
658         * @param clss
659         * @param child
660         * @return a Style for a line symbol
661         */
662        private Rule createPolygonBMPRule( Map clss, Map child, String filterCol )
663                                throws Exception {
664    
665            if ( clss.get( "IsNoData" ) != null ) {
666                return null;
667            }
668    
669            String label = (String) clss.get( "Label" );
670            label = StringTools.validateString( label, "\"" );
671    
672            Filter filter = null;
673            if ( filterCol != null ) {
674                filter = createFilter( clss, filterCol );
675            }
676            // get foreground color
677            String clrIdx = (String) child.get( "Color" );
678            Map color = (Map) blocks.get( "TClr." + clrIdx );
679            double opacity = 1f;
680            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
681                opacity = 0f;
682            }
683    
684            Color fgColor = createColor( color );
685            if ( fgColor == null ) {
686                fgColor = Color.BLACK;
687            }
688            // get color of the outlining stroke
689            clrIdx = (String) child.get( "OutlineColor" );
690            color = (Map) blocks.get( "TClr." + clrIdx );
691            double outLOpacity = 1f;
692            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
693                outLOpacity = 0f;
694            }
695            Color outLColor = createColor( color );
696            if ( outLColor == null ) {
697                outLColor = Color.BLACK;
698            }
699            // get background color
700            clrIdx = (String) child.get( "BgColor" );
701            color = (Map) blocks.get( "TClr." + clrIdx );
702            double bgOpacity = 1f;
703            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
704                bgOpacity = 0f;
705            }
706            Color bgColor = createColor( color );
707    
708            // create fill pattern as an image that will be referenced as
709            // external graphic
710            String stippleIdx = (String) child.get( "Stipple" );
711            String src = null;
712            if ( stippleIdx != null ) {
713                Map stipple = (Map) blocks.get( "Stipple." + stippleIdx );
714                src = createExternalGraphicFromStipple( stipple, label, fgColor, bgColor );
715            }
716    
717            double width = Double.parseDouble( (String) child.get( "OutlineWidth" ) ) + 1;
718            Stroke stroke = StyleFactory.createStroke( outLColor, width, outLOpacity, null, "mitre",
719                                                       "butt" );
720            Fill fill = null;
721            if ( stippleIdx != null ) {
722                ExternalGraphic eg = StyleFactory.createExternalGraphic( "file:///" + src, "image/gif" );
723                Graphic graph = StyleFactory.createGraphic( eg, null, opacity, 10, 0 );
724                GraphicFill gf = StyleFactory.createGraphicFill( graph );
725                fill = StyleFactory.createFill( fgColor, opacity, gf );
726            } else {
727                fill = StyleFactory.createFill( fgColor, opacity );
728            }
729            PolygonSymbolizer polySym = StyleFactory.createPolygonSymbolizer( stroke, fill );
730            return StyleFactory.createRule( new Symbolizer[] { polySym }, label, label, "", null,
731                                            filter, false, 0, 9E99 );
732        }
733    
734        /**
735         * creates a Style for a line symbol
736         * 
737         * @param clss
738         * @param child
739         * @return a Style for a line symbol
740         */
741        private Rule createPolygonVecRule( Map clss, Map child, String filterCol )
742                                throws Exception {
743    
744            if ( clss.get( "IsNoData" ) != null ) {
745                return null;
746            }
747    
748            String label = (String) clss.get( "Label" );
749            label = StringTools.validateString( label, "\"" );
750    
751            Filter filter = null;
752            if ( filterCol != null ) {
753                filter = createFilter( clss, filterCol );
754            }
755            // get foreground color
756            String clrIdx = (String) child.get( "Color" );
757            Map color = (Map) blocks.get( "TClr." + clrIdx );
758            double opacity = 1f;
759            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
760                opacity = 0f;
761            }
762            Color fgColor = createColor( color );
763            if ( fgColor == null ) {
764                fgColor = Color.BLACK;
765            }
766            // get color of the outlining stroke
767            clrIdx = (String) child.get( "OutlineColor" );
768            color = (Map) blocks.get( "TClr." + clrIdx );
769            double outLOpacity = 1f;
770            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
771                outLOpacity = 0f;
772            }
773            Color outLColor = createColor( color );
774            if ( outLColor == null ) {
775                outLColor = Color.BLACK;
776            }
777            // get background color
778            clrIdx = (String) child.get( "BgColor" );
779            color = (Map) blocks.get( "TClr." + clrIdx );
780            double bgOpacity = 1f;
781            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
782                bgOpacity = 0f;
783            }
784            Color bgColor = createColor( color );
785    
786            // create fill pattern as an image that will be referenced as
787            // external graphic
788            String stippleIdx = (String) child.get( "Stipple" );
789            String src = null;
790            if ( stippleIdx != null ) {
791                Map stipple = (Map) blocks.get( "Stipple." + stippleIdx );
792                src = createExternalGraphicFromStipple( stipple, label, fgColor, bgColor );
793            }
794    
795            double width = Double.parseDouble( (String) child.get( "OutlineWidth" ) ) + 1;
796            Stroke stroke = StyleFactory.createStroke( outLColor, width, outLOpacity, null, "mitre",
797                                                       "butt" );
798            Fill fill = null;
799            if ( stippleIdx != null ) {
800                ExternalGraphic eg = StyleFactory.createExternalGraphic( "file:///" + src, "image/gif" );
801                Graphic graph = StyleFactory.createGraphic( eg, null, opacity, 10, 0 );
802                GraphicFill gf = StyleFactory.createGraphicFill( graph );
803                fill = StyleFactory.createFill( fgColor, opacity, gf );
804            } else {
805                fill = StyleFactory.createFill( fgColor, opacity );
806            }
807            PolygonSymbolizer polySym = StyleFactory.createPolygonSymbolizer( stroke, fill );
808            return StyleFactory.createRule( new Symbolizer[] { polySym }, label, label, "", null,
809                                            filter, false, 0, 9E99 );
810        }
811    
812        /**
813         * creates an image from a stipple and stores it as a gif image. The method returns the full
814         * name of the stored image.
815         * 
816         * @param stipple
817         * @return an image from a stipple and stores it as a gif image. The method returns the full
818         *         name of the stored image.
819         */
820        private String createExternalGraphicFromStipple( Map stipple, String label, Color fg, Color bg )
821                                throws Exception {
822    
823            if ( label != null ) {
824                label = label.replace( ' ', '_' );
825                label = label.replace( '.', '_' );
826                label = label.replace( ';', '_' );
827                label = label.replace( ',', '_' );
828                label = label.replace( '-', '_' );
829                label = label.replace( ':', '_' );
830            }
831            String tmp = (String) stipple.get( "Columns" );
832            int cols = Integer.parseInt( tmp );
833            tmp = (String) stipple.get( "Rows" );
834            int rows = Integer.parseInt( tmp );
835    
836            List bList = (List) stipple.get( "Bits" );
837            StringBuffer bStr = new StringBuffer( 1000 );
838            for ( int i = 0; i < bList.size(); i++ ) {
839                String[] t = StringTools.toArray( ( (String) bList.get( i ) ).trim(), " ", false );
840                for ( int j = 0; j < t.length; j++ ) {
841                    bStr.append( t[j] );
842                }
843            }
844    
845            char[] ch = bStr.toString().toCharArray();
846    
847            BufferedImage bi = createFillPattern( cols, rows, ch, fg, bg );
848    
849            final String format = "gif";
850    
851            String[] t = StringTools.toArray( fileRootName, "/", false );
852            String base = t[t.length - 1];
853            StringBuffer fileName = new StringBuffer();
854            fileName.append( targetDir ).append( base );
855            if ( label != null ) {
856                fileName.append( "_" ).append( label ).append( "." ).append( format );
857            } else {
858                fileName.append( "." ).append( format );
859            }
860    
861            // FileOutputStream fos = new FileOutputStream( targetDir + base + '_' + label + "." +
862            // format );
863            FileOutputStream fos = new FileOutputStream( fileName.toString() );
864            ImageUtils.saveImage( bi, fos, format, 1.0f );
865            // Encoders.encodeGif(fos,bi);
866            fos.close();
867    
868            if ( targetDir.startsWith( "/" ) ) {
869                if ( label != null ) {
870                    return targetDir.substring( 1, targetDir.length() ) + base + '_' + label + "."
871                           + format;
872                } else {
873                    return targetDir.substring( 1, targetDir.length() ) + base + "." + format;
874                }
875            } else {
876                if ( label != null ) {
877                    return targetDir + base + '_' + label + "." + format;
878                } else {
879                    return targetDir + base + "." + format;
880                }
881    
882            }
883    
884        }
885    
886        /**
887         * creates a <tt>BufferedImage</tt> using the stipple contained in the passed char array.
888         * 
889         * @param cols
890         * @param rows
891         * @param ch
892         * @return a <tt>BufferedImage</tt> using the stipple contained in the passed char array.
893         */
894        private BufferedImage createFillPattern( int cols, int rows, char[] ch, Color fg, Color bg ) {
895            BufferedImage bi = new BufferedImage( cols, rows, BufferedImage.TYPE_INT_ARGB );
896            int cntChar = 0;
897            byte[] bTmp = null;
898            char chr = ' ';
899            int hexCnt = 0;
900            if ( cols % 8 != 0 ) {
901                hexCnt = ( cols / 8 + 1 ) * 8;
902            } else {
903                hexCnt = cols;
904            }
905            for ( int i = 0; i < rows; i++ ) {
906                for ( int j = 0; j < hexCnt; j++ ) {
907                    if ( j % 4 == 0 ) {
908                        chr = ch[cntChar++];
909                        bTmp = getBits( chr );
910                    }
911                    if ( j < cols ) {
912                        if ( bTmp[j % 4] == 0 ) {
913                            if ( bg != null ) {
914                                bi.setRGB( j, i, bg.getRGB() );
915                            }
916                        } else {
917                            bi.setRGB( j, i, fg.getRGB() );
918                        }
919                    }
920                }
921            }
922            return bi;
923        }
924    
925        private byte[] getBits( int ch ) {
926            switch ( ch ) {
927            case '0':
928                return new byte[] { 0, 0, 0, 0 };
929            case '1':
930                return new byte[] { 0, 0, 0, 1 };
931            case '2':
932                return new byte[] { 0, 0, 1, 0 };
933            case '3':
934                return new byte[] { 0, 0, 1, 1 };
935            case '4':
936                return new byte[] { 0, 1, 0, 0 };
937            case '5':
938                return new byte[] { 0, 1, 0, 1 };
939            case '6':
940                return new byte[] { 0, 1, 1, 0 };
941            case '7':
942                return new byte[] { 0, 1, 1, 1 };
943            case '8':
944                return new byte[] { 1, 0, 0, 0 };
945            case '9':
946                return new byte[] { 1, 0, 0, 1 };
947            case 'a':
948                return new byte[] { 1, 0, 1, 0 };
949            case 'b':
950                return new byte[] { 1, 0, 1, 1 };
951            case 'c':
952                return new byte[] { 1, 1, 0, 0 };
953            case 'd':
954                return new byte[] { 1, 1, 0, 1 };
955            case 'e':
956                return new byte[] { 1, 1, 1, 0 };
957            case 'f':
958                return new byte[] { 1, 1, 1, 1 };
959            default:
960                return new byte[] { 0, 0, 0, 0 };
961            }
962        }
963    
964        /**
965         * creates a dasharray for constructing a stroke from the pattern entries of a avl xxxSym. block
966         * 
967         * @param pl
968         * @return
969         */
970        private float[] createDashArray( List pl ) {
971            int cnt = 0;
972            for ( int i = 0; i < pl.size(); i++ ) {
973                if ( Float.parseFloat( (String) pl.get( i ) ) > 0 ) {
974                    cnt++;
975                } else {
976                    break;
977                }
978            }
979            float[] pattern = null;
980            if ( cnt > 0 ) {
981                pattern = new float[cnt];
982                for ( int i = 0; i < pattern.length; i++ ) {
983                    pattern[i] = Float.parseFloat( (String) pl.get( i ) );
984                }
985            }
986            return pattern;
987        }
988    
989        /**
990         * creates a AWT color from a val color block
991         * 
992         * @param color
993         * @return a AWT color from a val color block
994         */
995        private Color createColor( Map color ) {
996            StringBuffer hex = new StringBuffer( "0x" );
997            if ( color != null && !"\"Transparent\"".equals( color.get( "Name" ) ) ) {
998                String red = (String) color.get( "Red" );
999                if ( red == null )
1000                    red = "0x0000";
1001                int c = (int) ( ( Integer.decode( red ).intValue() / 65535f ) * 255 );
1002                if ( c < 15 ) {
1003                    hex.append( 0 );
1004                }
1005                hex.append( Integer.toHexString( c ) );
1006                String green = (String) color.get( "Green" );
1007                if ( green == null )
1008                    green = "0x0000";
1009                c = (int) ( ( Integer.decode( green ).intValue() / 65535f ) * 255 );
1010                if ( c < 15 ) {
1011                    hex.append( 0 );
1012                }
1013                hex.append( Integer.toHexString( c ) );
1014                String blue = (String) color.get( "Blue" );
1015                if ( blue == null )
1016                    blue = "0x0000";
1017                c = (int) ( ( Integer.decode( blue ).intValue() / 65535f ) * 255 );
1018                if ( c < 15 ) {
1019                    hex.append( 0 );
1020                }
1021                hex.append( Integer.toHexString( c ) );
1022            } else {
1023                // hex.append("000000");
1024                return null;
1025            }
1026            return Color.decode( hex.toString() );
1027        }
1028    
1029        /**
1030         * 
1031         * @param clss
1032         * @param filterCol
1033         * @return
1034         * @throws FilterConstructionException
1035         */
1036        private Filter createFilter( Map clss, String filterCol ) {
1037    
1038            if ( clss.get( "Label" ) == null ) {
1039                return null;
1040            }
1041            Filter filter = null;
1042    
1043            String maxN = (String) clss.get( "MaxStr" );
1044            String minN = (String) clss.get( "MinStr" );
1045            if ( maxN != null && minN != null ) {
1046                filter = createStringFilter( clss, filterCol );
1047            } else {
1048                filter = createNumberFilter( clss, filterCol );
1049            }
1050    
1051            return filter;
1052        }
1053    
1054        /**
1055         * @param filterCol
1056         * @param maxN
1057         * @param minN
1058         * @return
1059         * @throws FilterConstructionException
1060         */
1061        private Filter createStringFilter( Map clss, String filterCol ) {
1062            Filter filter;
1063            Operation oper = null;
1064            String maxN = (String) clss.get( "MaxStr" );
1065            String minN = (String) clss.get( "MinStr" );
1066            maxN = maxN.substring( 1, maxN.length() - 1 );
1067            minN = minN.substring( 1, minN.length() - 1 );
1068            if ( maxN.equals( minN ) ) {
1069                oper = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
1070                                                    new PropertyName( new QualifiedName( filterCol ) ), new Literal( minN ) );
1071            } else {
1072                ArrayList<Operation> list = new ArrayList<Operation>();
1073                Operation op = new PropertyIsCOMPOperation(
1074                                                            OperationDefines.PROPERTYISLESSTHANOREQUALTO,
1075                                                            new PropertyName( new QualifiedName( filterCol ) ),
1076                                                            new Literal( maxN ) );
1077                list.add( op );
1078                op = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISGREATERTHANOREQUALTO,
1079                                                  new PropertyName( new QualifiedName( filterCol ) ), new Literal( minN ) );
1080                list.add( op );
1081    
1082                oper = new LogicalOperation( OperationDefines.AND, list );
1083            }
1084            filter = new ComplexFilter( oper );
1085            return filter;
1086        }
1087    
1088        /**
1089         * @param clss
1090         * @param filterCol
1091         * @return
1092         * @throws FilterConstructionException
1093         */
1094        private Filter createNumberFilter( Map clss, String filterCol ) {
1095    
1096            String maxN = (String) clss.get( "MaxNum" );
1097            String minN = (String) clss.get( "MinNum" );
1098            if ( maxN == null ) {
1099                maxN = "9E99";
1100            }
1101            if ( minN == null ) {
1102                minN = "-9E99";
1103            }
1104            double t1 = Double.parseDouble( minN );
1105            double t2 = Double.parseDouble( maxN );
1106            Operation oper = null;
1107            if ( t1 == t2 ) {
1108                // if t1 == t2 no range is defined and so an 'is equal to'
1109                // opertaion must be used
1110                if ( ( (int) t1 ) == t1 ) {
1111                    // if no significant fraction values are present
1112                    // cast the value to int
1113                    oper = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
1114                                                        new PropertyName( new QualifiedName( filterCol ) ),
1115                                                        new Literal( "" + ( (int) t1 ) ) );
1116                } else {
1117                    oper = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
1118                                                        new PropertyName( new QualifiedName( filterCol  )),
1119                                                        new Literal( "" + t1 ) );
1120                }
1121            } else {
1122                // if t1 != t2 range of valid values is defined and so an so two
1123                // operation (one for each border of the range) are used
1124                ArrayList<Operation> list = new ArrayList<Operation>();
1125                Operation op = new PropertyIsCOMPOperation(
1126                                                            OperationDefines.PROPERTYISLESSTHANOREQUALTO,
1127                                                            new PropertyName( new QualifiedName( filterCol ) ),
1128                                                            new Literal( maxN ) );
1129                list.add( op );
1130                op = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISGREATERTHANOREQUALTO,
1131                                                  new PropertyName( new QualifiedName( filterCol ) ), new Literal( minN ) );
1132                list.add( op );
1133    
1134                oper = new LogicalOperation( OperationDefines.AND, list );
1135    
1136            }
1137    
1138            return new ComplexFilter( oper );
1139    
1140        }
1141    
1142        private static String[] getAVLFiles( String dir ) {
1143            File file = new File( dir );
1144            return file.list( new DFileFilter() );
1145    
1146        }
1147    
1148        private static void printHelp() {
1149    
1150            System.out.println( "Converts ESRI *.avl files to OGC SLD documents. The current version of " );
1151            System.out.println( "this tool isn't able to convert each and every construction that is " );
1152            System.out.println( "possible with an *.avl file. But most of the common expressions will be mapped." );
1153            System.out.println( "Supported are *.avl files for point, lines and polygons." );
1154            System.out.println( "" );
1155            System.out.println( "-sourceFile -> full path to the *.avl file to be converted. " );
1156            System.out.println( "           in the same directory a shapefile with the same name as the *.avl" );
1157            System.out.println( "               file must exist! (conditional, -sourceFile or -sourceDir must " );
1158            System.out.println( "               be defined)" );
1159            System.out.println( "-sourceDir  -> directory containing one or more *.avl files. for each existing" );
1160            System.out.println( "               *.avl file a shapefile with the same base name must exist. " );
1161            System.out.println( "               (conditional, -sourceFile or -sourceDir must be defined)" );
1162            System.out.println( "-targetDir  -> directory where the created SLD document(s) will be stored. (optional)" );
1163            System.out.println( "-h  -> print this help" );
1164        }
1165    
1166        /**
1167         * 
1168         * @param args
1169         * @throws Exception
1170         */
1171        public static void main( String[] args )
1172                                throws Exception {
1173    
1174            Map<String,String> map = new HashMap<String,String>();
1175    
1176            for ( int i = 0; i < args.length; i += 2 ) {
1177                map.put( args[i], args[i + 1] );
1178            }
1179    
1180            if ( map.get( "-sourceFile" ) == null && map.get( "-sourceDir" ) == null
1181                 && map.get( "-h" ) == null ) {
1182                System.out.println( "-sourceFile or -sourceDir must be defined!" );
1183                System.out.println();
1184                printHelp();
1185                System.exit( 1 );
1186            }
1187    
1188            if ( map.get( "-h" ) != null ) {
1189                printHelp();
1190                System.exit( 0 );
1191            }
1192    
1193            String targetDir = ".";
1194            String[] sourceFiles = null;
1195            if ( map.get( "-sourceFile" ) != null ) {
1196                sourceFiles = new String[] { map.get( "-sourceFile" ) };
1197                // set the default target directory to the sourceFile's directory
1198                targetDir = sourceFiles[0].substring( 0, sourceFiles[0].lastIndexOf( "/" ) );
1199            }
1200    
1201            if ( sourceFiles == null ) {
1202                String sourceDir = map.get( "-sourceDir" );
1203                sourceFiles = getAVLFiles( sourceDir );
1204                for ( int i = 0; i < sourceFiles.length; i++ ) {
1205                    sourceFiles[i] = sourceDir + '/' + sourceFiles[i];
1206                }
1207                // set the default target directory to the source directory
1208                targetDir = sourceDir;
1209    
1210            }
1211    
1212            // String targetDir = ".";
1213            if ( map.get( "-targetDir" ) != null ) {
1214                targetDir = map.get( "-targetDir" );
1215            }
1216    
1217            for ( int i = 0; i < sourceFiles.length; i++ ) {
1218                System.out.println( "processing: " + sourceFiles[i] );
1219                int pos = sourceFiles[i].lastIndexOf( '.' );
1220                String file = sourceFiles[i].substring( 0, pos );
1221                AVL2SLD avl = new AVL2SLD( file, targetDir );
1222                avl.read();
1223                StyledLayerDescriptor sld = avl.getStyledLayerDescriptor();
1224                String[] t = StringTools.toArray( file, "/", false );
1225                FileWriter fos = new FileWriter( targetDir + '/' + t[t.length - 1] + ".xml" );
1226                fos.write( ( (Marshallable) sld ).exportAsXML() );
1227                fos.close();
1228            }
1229        }
1230    
1231        /**
1232         * 
1233         * 
1234         * @version $Revision: 9346 $
1235         * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1236         */
1237        private static class DFileFilter implements FilenameFilter {
1238            /**
1239             * @param f
1240             * @return
1241             */
1242            public boolean accept( File f, String name ) {
1243                int pos = name.lastIndexOf( "." );
1244                String ext = name.substring( pos + 1 );
1245                return ext.toUpperCase().equals( "AVL" );
1246            }
1247        }
1248    
1249    }