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