001    //$$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/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: mschneider $
112     *
113     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
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                    rule = createPolygonVecRule( clss, child, filterCol );
619                } else {
620                    rule = createPolygonBMPRule( clss, child, filterCol );
621                }
622                if ( rule != null ) {
623                    list.add( rule );
624                }
625                // TODO
626                // write special method for vector polygon fill
627            }
628            Rule[] rules = list.toArray( new Rule[list.size()] );
629            FeatureTypeStyle fts = StyleFactory.createFeatureTypeStyle( rules );
630    
631            String[] t = StringTools.toArray( fileRootName, "/", false );
632            String name = "default:" + t[t.length - 1];
633            return StyleFactory.createStyle( name, null, null, fts );
634        }
635    
636        /**
637         * creates a Style for a line symbol
638         *
639         * @param clss
640         * @param child
641         * @return a Style for a line symbol
642         */
643        private Rule createPolygonBMPRule( Map clss, Map<String, Object> child, String filterCol )
644                                throws Exception {
645    
646            if ( clss.get( "IsNoData" ) != null ) {
647                return null;
648            }
649    
650            String label = (String) clss.get( "Label" );
651            label = StringTools.validateString( label, "\"" );
652    
653            Filter filter = null;
654            if ( filterCol != null ) {
655                filter = createFilter( clss, filterCol );
656            }
657            // get foreground color
658            String clrIdx = (String) child.get( "Color" );
659            Map<String, Object> color = blocks.get( "TClr." + clrIdx );
660            double opacity = 1f;
661            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
662                opacity = 0f;
663            }
664    
665            Color fgColor = createColor( color );
666            if ( fgColor == null ) {
667                fgColor = Color.BLACK;
668            }
669            // get color of the outlining stroke
670            clrIdx = (String) child.get( "OutlineColor" );
671            color = blocks.get( "TClr." + clrIdx );
672            double outLOpacity = 1f;
673            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
674                outLOpacity = 0f;
675            }
676            Color outLColor = createColor( color );
677            if ( outLColor == null ) {
678                outLColor = Color.BLACK;
679            }
680            // get background color
681            clrIdx = (String) child.get( "BgColor" );
682            color = blocks.get( "TClr." + clrIdx );
683            double bgOpacity = 1f;
684            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
685                bgOpacity = 0f;
686            }
687            Color bgColor = createColor( color );
688    
689            // create fill pattern as an image that will be referenced as
690            // external graphic
691            String stippleIdx = (String) child.get( "Stipple" );
692            String src = null;
693            if ( stippleIdx != null ) {
694                Map stipple = blocks.get( "Stipple." + stippleIdx );
695                src = createExternalGraphicFromStipple( stipple, label, fgColor, bgColor );
696            }
697    
698            double width = Double.parseDouble( (String) child.get( "OutlineWidth" ) ) + 1;
699            Stroke stroke = StyleFactory.createStroke( outLColor, width, outLOpacity, null, "mitre", "butt" );
700            Fill fill = null;
701            if ( stippleIdx != null ) {
702                ExternalGraphic eg = StyleFactory.createExternalGraphic( "file:///" + src, "image/gif" );
703                Graphic graph = StyleFactory.createGraphic( eg, null, opacity, 10, 0 );
704                GraphicFill gf = StyleFactory.createGraphicFill( graph );
705                fill = StyleFactory.createFill( fgColor, opacity, gf );
706            } else {
707                fill = StyleFactory.createFill( fgColor, opacity );
708            }
709            PolygonSymbolizer polySym = StyleFactory.createPolygonSymbolizer( stroke, fill );
710            return StyleFactory.createRule( new Symbolizer[] { polySym }, label, label, "", null, filter, false, 0, 9E99 );
711        }
712    
713        /**
714         * creates a Style for a line symbol
715         *
716         * @param clss
717         * @param child
718         * @return a Style for a line symbol
719         */
720        private Rule createPolygonVecRule( Map clss, Map<String, Object> child, String filterCol )
721                                throws Exception {
722    
723            if ( clss.get( "IsNoData" ) != null ) {
724                return null;
725            }
726    
727            String label = (String) clss.get( "Label" );
728            label = StringTools.validateString( label, "\"" );
729    
730            Filter filter = null;
731            if ( filterCol != null ) {
732                filter = createFilter( clss, filterCol );
733            }
734            // get foreground color
735            String clrIdx = (String) child.get( "Color" );
736            Map<String, Object> color = blocks.get( "TClr." + clrIdx );
737            double opacity = 1f;
738            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
739                opacity = 0f;
740            }
741            Color fgColor = createColor( color );
742            if ( fgColor == null ) {
743                fgColor = Color.BLACK;
744            }
745            // get color of the outlining stroke
746            clrIdx = (String) child.get( "OutlineColor" );
747            color = blocks.get( "TClr." + clrIdx );
748            double outLOpacity = 1f;
749            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
750                outLOpacity = 0f;
751            }
752            Color outLColor = createColor( color );
753            if ( outLColor == null ) {
754                outLColor = Color.BLACK;
755            }
756            // get background color
757            clrIdx = (String) child.get( "BgColor" );
758            color = blocks.get( "TClr." + clrIdx );
759            double bgOpacity = 1f;
760            if ( color != null && "\"Transparent\"".equals( color.get( "Name" ) ) ) {
761                bgOpacity = 0f;
762            }
763            Color bgColor = createColor( color );
764    
765            // create fill pattern as an image that will be referenced as
766            // external graphic
767            String stippleIdx = (String) child.get( "Stipple" );
768            String src = null;
769            if ( stippleIdx != null ) {
770                Map stipple = blocks.get( "Stipple." + stippleIdx );
771                src = createExternalGraphicFromStipple( stipple, label, fgColor, bgColor );
772            }
773    
774            double width = Double.parseDouble( (String) child.get( "OutlineWidth" ) ) + 1;
775            Stroke stroke = StyleFactory.createStroke( outLColor, width, outLOpacity, null, "mitre", "butt" );
776            Fill fill = null;
777            if ( stippleIdx != null ) {
778                ExternalGraphic eg = StyleFactory.createExternalGraphic( "file:///" + src, "image/gif" );
779                Graphic graph = StyleFactory.createGraphic( eg, null, opacity, 10, 0 );
780                GraphicFill gf = StyleFactory.createGraphicFill( graph );
781                fill = StyleFactory.createFill( fgColor, opacity, gf );
782            } else {
783                fill = StyleFactory.createFill( fgColor, opacity );
784            }
785            PolygonSymbolizer polySym = StyleFactory.createPolygonSymbolizer( stroke, fill );
786            return StyleFactory.createRule( new Symbolizer[] { polySym }, label, label, "", null, filter, false, 0, 9E99 );
787        }
788    
789        /**
790         * creates an image from a stipple and stores it as a gif image. The method returns the full
791         * name of the stored image.
792         *
793         * @param stipple
794         * @return an image from a stipple and stores it as a gif image. The method returns the full
795         *         name of the stored image.
796         */
797        private String createExternalGraphicFromStipple( Map stipple, String label, Color fg, Color bg )
798                                throws Exception {
799    
800            if ( label != null ) {
801                label = label.replace( ' ', '_' );
802                label = label.replace( '.', '_' );
803                label = label.replace( ';', '_' );
804                label = label.replace( ',', '_' );
805                label = label.replace( '-', '_' );
806                label = label.replace( ':', '_' );
807            }
808            String tmp = (String) stipple.get( "Columns" );
809            int cols = Integer.parseInt( tmp );
810            tmp = (String) stipple.get( "Rows" );
811            int rows = Integer.parseInt( tmp );
812    
813            List bList = (List) stipple.get( "Bits" );
814            StringBuffer bStr = new StringBuffer( 1000 );
815            for ( int i = 0; i < bList.size(); i++ ) {
816                String[] t = StringTools.toArray( ( (String) bList.get( i ) ).trim(), " ", false );
817                for ( int j = 0; j < t.length; j++ ) {
818                    bStr.append( t[j] );
819                }
820            }
821    
822            char[] ch = bStr.toString().toCharArray();
823    
824            BufferedImage bi = createFillPattern( cols, rows, ch, fg, bg );
825    
826            final String format = "gif";
827    
828            String[] t = StringTools.toArray( fileRootName, "/", false );
829            String base = t[t.length - 1];
830            StringBuffer fileName = new StringBuffer();
831            fileName.append( targetDir ).append( base );
832            if ( label != null ) {
833                fileName.append( "_" ).append( label ).append( "." ).append( format );
834            } else {
835                fileName.append( "." ).append( format );
836            }
837    
838            // FileOutputStream fos = new FileOutputStream( targetDir + base + '_' + label + "." +
839            // format );
840            FileOutputStream fos = new FileOutputStream( fileName.toString() );
841            ImageUtils.saveImage( bi, fos, format, 1.0f );
842            // Encoders.encodeGif(fos,bi);
843            fos.close();
844    
845            if ( targetDir.startsWith( "/" ) ) {
846                if ( label != null ) {
847                    return targetDir.substring( 1, targetDir.length() ) + base + '_' + label + "." + format;
848                } else {
849                    return targetDir.substring( 1, targetDir.length() ) + base + "." + format;
850                }
851            } else {
852                if ( label != null ) {
853                    return targetDir + base + '_' + label + "." + format;
854                } else {
855                    return targetDir + base + "." + format;
856                }
857    
858            }
859    
860        }
861    
862        /**
863         * creates a <tt>BufferedImage</tt> using the stipple contained in the passed char array.
864         *
865         * @param cols
866         * @param rows
867         * @param ch
868         * @return a <tt>BufferedImage</tt> using the stipple contained in the passed char array.
869         */
870        private BufferedImage createFillPattern( int cols, int rows, char[] ch, Color fg, Color bg ) {
871            BufferedImage bi = new BufferedImage( cols, rows, BufferedImage.TYPE_INT_ARGB );
872            int cntChar = 0;
873            byte[] bTmp = null;
874            char chr = ' ';
875            int hexCnt = 0;
876            if ( cols % 8 != 0 ) {
877                hexCnt = ( cols / 8 + 1 ) * 8;
878            } else {
879                hexCnt = cols;
880            }
881            for ( int i = 0; i < rows; i++ ) {
882                for ( int j = 0; j < hexCnt; j++ ) {
883                    if ( j % 4 == 0 ) {
884                        chr = ch[cntChar++];
885                        bTmp = getBits( chr );
886                    }
887                    if ( j < cols ) {
888                        if ( bTmp[j % 4] == 0 ) {
889                            if ( bg != null ) {
890                                bi.setRGB( j, i, bg.getRGB() );
891                            }
892                        } else {
893                            bi.setRGB( j, i, fg.getRGB() );
894                        }
895                    }
896                }
897            }
898            return bi;
899        }
900    
901        private byte[] getBits( int ch ) {
902            switch ( ch ) {
903            case '0':
904                return new byte[] { 0, 0, 0, 0 };
905            case '1':
906                return new byte[] { 0, 0, 0, 1 };
907            case '2':
908                return new byte[] { 0, 0, 1, 0 };
909            case '3':
910                return new byte[] { 0, 0, 1, 1 };
911            case '4':
912                return new byte[] { 0, 1, 0, 0 };
913            case '5':
914                return new byte[] { 0, 1, 0, 1 };
915            case '6':
916                return new byte[] { 0, 1, 1, 0 };
917            case '7':
918                return new byte[] { 0, 1, 1, 1 };
919            case '8':
920                return new byte[] { 1, 0, 0, 0 };
921            case '9':
922                return new byte[] { 1, 0, 0, 1 };
923            case 'a':
924                return new byte[] { 1, 0, 1, 0 };
925            case 'b':
926                return new byte[] { 1, 0, 1, 1 };
927            case 'c':
928                return new byte[] { 1, 1, 0, 0 };
929            case 'd':
930                return new byte[] { 1, 1, 0, 1 };
931            case 'e':
932                return new byte[] { 1, 1, 1, 0 };
933            case 'f':
934                return new byte[] { 1, 1, 1, 1 };
935            default:
936                return new byte[] { 0, 0, 0, 0 };
937            }
938        }
939    
940        /**
941         * @return creates a dasharray for constructing a stroke from the pattern entries of a avl
942         *         xxxSym. block
943         *
944         * @param pl
945         */
946        private float[] createDashArray( List<String> pl ) {
947            int cnt = 0;
948            for ( int i = 0; i < pl.size(); i++ ) {
949                if ( Float.parseFloat( pl.get( i ) ) > 0 ) {
950                    cnt++;
951                } else {
952                    break;
953                }
954            }
955            float[] pattern = null;
956            if ( cnt > 0 ) {
957                pattern = new float[cnt];
958                for ( int i = 0; i < pattern.length; i++ ) {
959                    pattern[i] = Float.parseFloat( pl.get( i ) );
960                }
961            }
962            return pattern;
963        }
964    
965        /**
966         * creates a AWT color from a val color block
967         *
968         * @param color
969         * @return a AWT color from a val color block
970         */
971        private Color createColor( Map<String, Object> color ) {
972            StringBuffer hex = new StringBuffer( "0x" );
973            if ( color != null && !"\"Transparent\"".equals( color.get( "Name" ) ) ) {
974                String red = (String) color.get( "Red" );
975                if ( red == null )
976                    red = "0x0000";
977                int c = (int) ( ( Integer.decode( red ).intValue() / 65535f ) * 255 );
978                if ( c < 15 ) {
979                    hex.append( 0 );
980                }
981                hex.append( Integer.toHexString( c ) );
982                String green = (String) color.get( "Green" );
983                if ( green == null )
984                    green = "0x0000";
985                c = (int) ( ( Integer.decode( green ).intValue() / 65535f ) * 255 );
986                if ( c < 15 ) {
987                    hex.append( 0 );
988                }
989                hex.append( Integer.toHexString( c ) );
990                String blue = (String) color.get( "Blue" );
991                if ( blue == null )
992                    blue = "0x0000";
993                c = (int) ( ( Integer.decode( blue ).intValue() / 65535f ) * 255 );
994                if ( c < 15 ) {
995                    hex.append( 0 );
996                }
997                hex.append( Integer.toHexString( c ) );
998            } else {
999                // hex.append("000000");
1000                return null;
1001            }
1002            return Color.decode( hex.toString() );
1003        }
1004    
1005        private Filter createFilter( Map clss, String filterCol ) {
1006    
1007            if ( clss.get( "Label" ) == null ) {
1008                return null;
1009            }
1010            Filter filter = null;
1011    
1012            String maxN = (String) clss.get( "MaxStr" );
1013            String minN = (String) clss.get( "MinStr" );
1014            if ( maxN != null && minN != null ) {
1015                filter = createStringFilter( clss, filterCol );
1016            } else {
1017                filter = createNumberFilter( clss, filterCol );
1018            }
1019    
1020            return filter;
1021        }
1022    
1023        private Filter createStringFilter( Map clss, String filterCol ) {
1024            Filter filter;
1025            Operation oper = null;
1026            String maxN = (String) clss.get( "MaxStr" );
1027            String minN = (String) clss.get( "MinStr" );
1028            maxN = maxN.substring( 1, maxN.length() - 1 );
1029            minN = minN.substring( 1, minN.length() - 1 );
1030            if ( maxN.equals( minN ) ) {
1031                oper = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
1032                                                    new PropertyName( new QualifiedName( filterCol ) ), new Literal( minN ) );
1033            } else {
1034                ArrayList<Operation> list = new ArrayList<Operation>();
1035                Operation op = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISLESSTHANOREQUALTO,
1036                                                            new PropertyName( new QualifiedName( filterCol ) ),
1037                                                            new Literal( maxN ) );
1038                list.add( op );
1039                op = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISGREATERTHANOREQUALTO,
1040                                                  new PropertyName( new QualifiedName( filterCol ) ), new Literal( minN ) );
1041                list.add( op );
1042    
1043                oper = new LogicalOperation( OperationDefines.AND, list );
1044            }
1045            filter = new ComplexFilter( oper );
1046            return filter;
1047        }
1048    
1049        private Filter createNumberFilter( Map clss, String filterCol ) {
1050    
1051            String maxN = (String) clss.get( "MaxNum" );
1052            String minN = (String) clss.get( "MinNum" );
1053            if ( maxN == null ) {
1054                maxN = "9E99";
1055            }
1056            if ( minN == null ) {
1057                minN = "-9E99";
1058            }
1059            double t1 = Double.parseDouble( minN );
1060            double t2 = Double.parseDouble( maxN );
1061            Operation oper = null;
1062            if ( t1 == t2 ) {
1063                // if t1 == t2 no range is defined and so an 'is equal to'
1064                // opertaion must be used
1065                if ( ( (int) t1 ) == t1 ) {
1066                    // if no significant fraction values are present
1067                    // cast the value to int
1068                    oper = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
1069                                                        new PropertyName( new QualifiedName( filterCol ) ),
1070                                                        new Literal( "" + ( (int) t1 ) ) );
1071                } else {
1072                    oper = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
1073                                                        new PropertyName( new QualifiedName( filterCol ) ),
1074                                                        new Literal( "" + t1 ) );
1075                }
1076            } else {
1077                // if t1 != t2 range of valid values is defined and so an so two
1078                // operation (one for each border of the range) are used
1079                ArrayList<Operation> list = new ArrayList<Operation>();
1080                Operation op = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISLESSTHANOREQUALTO,
1081                                                            new PropertyName( new QualifiedName( filterCol ) ),
1082                                                            new Literal( maxN ) );
1083                list.add( op );
1084                op = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISGREATERTHANOREQUALTO,
1085                                                  new PropertyName( new QualifiedName( filterCol ) ), new Literal( minN ) );
1086                list.add( op );
1087    
1088                oper = new LogicalOperation( OperationDefines.AND, list );
1089    
1090            }
1091    
1092            return new ComplexFilter( oper );
1093    
1094        }
1095    
1096        private static String[] getAVLFiles( String dir ) {
1097            File file = new File( dir );
1098            return file.list( new DFileFilter() );
1099    
1100        }
1101    
1102        private static void printHelp() {
1103    
1104            System.out.println( "Converts ESRI *.avl files to OGC SLD documents. The current version of " );
1105            System.out.println( "this tool isn't able to convert each and every construction that is " );
1106            System.out.println( "possible with an *.avl file. But most of the common expressions will be mapped." );
1107            System.out.println( "Supported are *.avl files for point, lines and polygons." );
1108            System.out.println( "" );
1109            System.out.println( "-sourceFile -> full path to the *.avl file to be converted. " );
1110            System.out.println( "           in the same directory a shapefile with the same name as the *.avl" );
1111            System.out.println( "               file must exist! (conditional, -sourceFile or -sourceDir must " );
1112            System.out.println( "               be defined)" );
1113            System.out.println( "-sourceDir  -> directory containing one or more *.avl files. for each existing" );
1114            System.out.println( "               *.avl file a shapefile with the same base name must exist. " );
1115            System.out.println( "               (conditional, -sourceFile or -sourceDir must be defined)" );
1116            System.out.println( "-targetDir  -> directory where the created SLD document(s) will be stored. (optional)" );
1117            System.out.println( "-h  -> print this help" );
1118        }
1119    
1120        /**
1121         *
1122         * @param args
1123         * @throws Exception
1124         */
1125        public static void main( String[] args )
1126                                throws Exception {
1127    
1128            Map<String, String> map = new HashMap<String, String>();
1129    
1130            for ( int i = 0; i < args.length; i += 2 ) {
1131                map.put( args[i], args[i + 1] );
1132            }
1133    
1134            if ( map.get( "-sourceFile" ) == null && map.get( "-sourceDir" ) == null && map.get( "-h" ) == null ) {
1135                System.out.println( "-sourceFile or -sourceDir must be defined!" );
1136                System.out.println();
1137                printHelp();
1138                System.exit( 1 );
1139            }
1140    
1141            if ( map.get( "-h" ) != null ) {
1142                printHelp();
1143                System.exit( 0 );
1144            }
1145    
1146            String targetDir = ".";
1147            String[] sourceFiles = null;
1148            if ( map.get( "-sourceFile" ) != null ) {
1149                sourceFiles = new String[] { map.get( "-sourceFile" ) };
1150                // set the default target directory to the sourceFile's directory
1151                targetDir = sourceFiles[0].substring( 0, sourceFiles[0].lastIndexOf( "/" ) );
1152            }
1153    
1154            if ( sourceFiles == null ) {
1155                String sourceDir = map.get( "-sourceDir" );
1156                sourceFiles = getAVLFiles( sourceDir );
1157                for ( int i = 0; i < sourceFiles.length; i++ ) {
1158                    sourceFiles[i] = sourceDir + '/' + sourceFiles[i];
1159                }
1160                // set the default target directory to the source directory
1161                targetDir = sourceDir;
1162    
1163            }
1164    
1165            // String targetDir = ".";
1166            if ( map.get( "-targetDir" ) != null ) {
1167                targetDir = map.get( "-targetDir" );
1168            }
1169    
1170            for ( int i = 0; i < sourceFiles.length; i++ ) {
1171                System.out.println( "processing: " + sourceFiles[i] );
1172                int pos = sourceFiles[i].lastIndexOf( '.' );
1173                String file = sourceFiles[i].substring( 0, pos );
1174                AVL2SLD avl = new AVL2SLD( file, targetDir );
1175                avl.read();
1176                StyledLayerDescriptor sld = avl.getStyledLayerDescriptor();
1177                String[] t = StringTools.toArray( file, "/", false );
1178                FileWriter fos = new FileWriter( targetDir + '/' + t[t.length - 1] + ".xml" );
1179                fos.write( ( (Marshallable) sld ).exportAsXML() );
1180                fos.close();
1181            }
1182        }
1183    
1184        /**
1185         *
1186         *
1187         * @version $Revision: 18195 $
1188         * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1189         */
1190        private static class DFileFilter implements FilenameFilter {
1191            /**
1192             * @param f
1193             * @return true if accepted
1194             */
1195            public boolean accept( File f, String name ) {
1196                int pos = name.lastIndexOf( "." );
1197                String ext = name.substring( pos + 1 );
1198                return ext.toUpperCase().equals( "AVL" );
1199            }
1200        }
1201    
1202    }