001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/crs/configuration/proj4/ProjFileResource.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.crs.configuration.proj4;
038    
039    import java.io.BufferedReader;
040    import java.io.FileNotFoundException;
041    import java.io.FileReader;
042    import java.io.IOException;
043    import java.io.StreamTokenizer;
044    import java.io.StringReader;
045    import java.util.HashMap;
046    import java.util.Map;
047    import java.util.Properties;
048    import java.util.Set;
049    
050    import org.deegree.crs.configuration.resources.CRSResource;
051    import org.deegree.crs.coordinatesystems.CoordinateSystem;
052    import org.deegree.crs.coordinatesystems.GeographicCRS;
053    import org.deegree.crs.exceptions.CRSConfigurationException;
054    import org.deegree.crs.transformations.Transformation;
055    import org.deegree.crs.transformations.helmert.Helmert;
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.i18n.Messages;
059    
060    /**
061     * The <code>ProjFileResource</code> class TODO add class documentation here.
062     *
063     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
064     *
065     * @author last edited by: $Author: mschneider $
066     *
067     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
068     *
069     */
070    public class ProjFileResource implements CRSResource<Map<String, String>> {
071    
072        private Map<String, Map<String, String>> idToParams = new HashMap<String, Map<String, String>>( 4000 );
073    
074        private static ILogger LOG = LoggerFactory.getLogger( ProjFileResource.class );
075    
076        /**
077         * @param provider
078         * @param properties
079         */
080        public ProjFileResource( PROJ4CRSProvider provider, Properties properties ) {
081            String fileName = properties.getProperty( "crs.configuration" );
082            try {
083                BufferedReader reader = new BufferedReader( new FileReader( fileName ) );
084                String line = reader.readLine();
085                Map<String, String> kvp = new HashMap<String, String>( 15 );
086                int lineNumber = 1;
087                while ( line != null ) {
088                    if ( line.startsWith( "#" ) ) {
089                        // remove the '#' from the String.
090                        if ( kvp.get( "comment" ) != null ) {
091                            LOG.logDebug( "(Line: " + lineNumber + ") Multiple comments found, removing previous: "
092                                          + kvp.get( "comment" ) );
093                        }
094                        kvp.put( "comment", line.substring( 1 ).trim() );
095                    } else {
096                        String identifier = parseConfigString( line, Integer.toString( lineNumber ), kvp );
097                        if ( identifier != null && !"".equals( identifier.trim() ) ) {
098                            LOG.logDebug( "Found identifier: " + identifier + " with following params: " + kvp );
099                            idToParams.put( identifier, kvp );
100                        }
101                        kvp = new HashMap<String, String>( 15 );
102                    }
103                    line = reader.readLine();
104                    lineNumber++;
105                }
106                reader.close();
107    
108            } catch ( FileNotFoundException e ) {
109                e.printStackTrace();
110            } catch ( IOException e ) {
111                LOG.logError( "Could not open file: " + fileName, e );
112                throw new CRSConfigurationException( e );
113                // e.printStackTrace();
114            }
115        }
116    
117        public Helmert getWGS84Transformation( GeographicCRS sourceCRS ) {
118            if ( sourceCRS == null ) {
119                return null;
120            }
121            for ( String id : sourceCRS.getIdentifiers() ) {
122                Map<String, String> params = null;
123                try {
124                    params = getURIAsType( id );
125                } catch ( IOException e ) {
126                    throw new CRSConfigurationException( e );
127                }
128                if ( params != null ) {
129                    return createWGS84ConversionInfo( sourceCRS, params );
130                }
131            }
132            return null;
133        }
134    
135        public Map<String, String> getURIAsType( String uri )
136                                throws IOException {
137            String tmpID = getIDCode( uri );
138            if ( LOG.isDebug() ) {
139                LOG.logDebug( "Given id: " + uri + " converted into: " + tmpID );
140            }
141            Map<String, String> result = idToParams.get( tmpID );
142            if ( result != null ) {
143                result = new HashMap<String, String>( result );
144            }
145            return result;
146        }
147    
148        /**
149         * @return a set containing all available ids.
150         */
151        public Set<String> getAvailableIDs() {
152            return idToParams.keySet();
153        }
154    
155        /**
156         * Creating the wgs84 aka BursaWolf conversion parameters. Either 3 or 7 parameters are supported.
157         *
158         * @param params
159         *            to get the towgs84 param from
160         * @return the conversion info from the params or <code>null<code> if no conversion info is available.
161         * @throws CRSConfigurationException
162         *             if the number of params are not 3 or 7.
163         */
164        private Helmert createWGS84ConversionInfo( CoordinateSystem sourceCRS, Map<String, String> params )
165                                throws CRSConfigurationException {
166            Helmert result = null;
167            String tmpValue = params.remove( "towgs84" );
168            if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
169                double[] values = null;
170                String[] splitter = tmpValue.trim().split( "," );
171                if ( splitter != null && splitter.length > 0 ) {
172                    values = new double[splitter.length];
173                    for ( int i = 0; i < splitter.length; ++i ) {
174                        values[i] = Double.parseDouble( splitter[i] );
175                    }
176                }
177                if ( values != null ) {
178                    String description = "Handmade proj4 towgs84 definition (parsed from nad/epsg) used by crs with id: "
179                                         + sourceCRS.getIdentifier() + "identifier";
180                    String name = "Proj4 defined toWGS84 params";
181    
182                    String id = Transformation.createFromTo( sourceCRS.getIdentifier(), GeographicCRS.WGS84.getIdentifier() );
183    
184                    if ( values.length == 3 ) {
185                        result = new Helmert( values[0], values[1], values[2], 0, 0, 0, 0, sourceCRS, GeographicCRS.WGS84,
186                                              id, name, sourceCRS.getVersion(), description, sourceCRS.getAreaOfUse() );
187                    } else if ( values.length == 7 ) {
188                        result = new Helmert( values[0], values[1], values[2], values[3], values[4], values[5], values[6],
189                                              sourceCRS, GeographicCRS.WGS84, id, name, sourceCRS.getVersion(),
190                                              description, sourceCRS.getAreaOfUse() );
191                    } else {
192                        throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_WGS84_PARAMS",
193                                                                                  sourceCRS.getIdentifier() + "identifier",
194                                                                                  Integer.toString( values.length ) ) );
195                    }
196                }
197            }
198            return result;
199        }
200    
201        /**
202         * Parses the configured proj4 parameters from the given String using a StreamTokenizer and saves them in the Map.
203         *
204         * @param params
205         *            to be parsed
206         * @param lineNumber
207         *            in the config file.
208         * @param kvp
209         *            in which the key-value pairs will be saved.
210         * @return the parsed Identifier or <code>null</code> if no identifier was found.
211         * @throws IOException
212         *             if the StreamTokenizer finds an error.
213         * @throws CRSConfigurationException
214         *             If the config was malformed.
215         */
216        private String parseConfigString( String params, String lineNumber, Map<String, String> kvp )
217                                throws IOException, CRSConfigurationException {
218            BufferedReader br = new BufferedReader( new StringReader( params ) );
219            StreamTokenizer t = new StreamTokenizer( br );
220            t.commentChar( '#' );
221            t.ordinaryChars( '0', '9' );
222            // t.ordinaryChars( '.', '.' );
223            t.ordinaryChar( '.' );
224            // t.ordinaryChars( '-', '-' );
225            t.ordinaryChar( '-' );
226            // t.ordinaryChars( '+', '+' );
227            t.ordinaryChar( '+' );
228            t.wordChars( '0', '9' );
229            t.wordChars( '\'', '\'' );
230            t.wordChars( '"', '"' );
231            t.wordChars( '_', '_' );
232            t.wordChars( '.', '.' );
233            t.wordChars( '-', '-' );
234            t.wordChars( '+', '+' );
235            t.wordChars( ',', ',' );
236            t.nextToken();
237            String identifier = null;
238            /**
239             * PROJ4 type definitions have following format, <number> +proj .... So the first type must start with an '<'
240             */
241            if ( t.ttype == '<' ) {
242                t.nextToken();
243                // if the next value is not a word, e.g. a number (see t.wordChars('0','9')) it is the
244                // wrong format.
245                if ( t.ttype != StreamTokenizer.TT_WORD ) {
246                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_INVALID_ID", lineNumber,
247                                                                              "An identifier (e.g. number)", "<",
248                                                                              getTokenizerSymbolToString( t.ttype ) ) );
249                }
250                // it's a word so get the identifier.
251                identifier = t.sval;
252                //
253                kvp.put( "identifier", identifier );
254                t.nextToken();
255    
256                // check for closing bracket.
257                if ( t.ttype != '>' ) {
258                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_MISSING_EXPECTED_CHAR",
259                                                                              lineNumber, ">" ) );
260                }
261                t.nextToken();
262    
263                // get the parameters.
264                while ( t.ttype != '<' ) {
265                    if ( t.ttype == '+' ) {
266                        t.nextToken();
267                    }
268                    if ( t.ttype != StreamTokenizer.TT_WORD ) {
269                        throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_INVALID_ID",
270                                                                                  lineNumber, "A parameter", "+",
271                                                                                  getTokenizerSymbolToString( t.ttype ) ) );
272                    }
273                    String key = t.sval;
274                    if ( key != null && !"".equals( key ) ) {
275                        if ( key.startsWith( "+" ) ) {
276                            key = key.substring( 1 );
277                        }
278                        t.nextToken();
279                        if ( t.ttype == '=' ) {
280                            t.nextToken();
281                            if ( t.ttype != StreamTokenizer.TT_WORD ) {
282                                throw new CRSConfigurationException(
283                                                                     Messages.getMessage(
284                                                                                          "CRS_CONFIG_PROJ4_INVALID_ID",
285                                                                                          lineNumber,
286                                                                                          "A Value",
287                                                                                          "=",
288                                                                                          getTokenizerSymbolToString( t.ttype ) ) );
289                            }
290                            String value = t.sval;
291                            LOG.logDebug( "Putting key: " + key + " with value: " + value );
292                            kvp.put( key, value );
293                            // take the next token.
294                            t.nextToken();
295                        }
296                    }
297                }
298                t.nextToken();
299                if ( t.ttype != '>' ) {
300                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_MISSING_EXPECTED_CHAR",
301                                                                              lineNumber, "<> (End of defintion)" ) );
302                }
303            } else {
304                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_MISSING_EXPECTED_CHAR",
305                                                                          lineNumber, "< (Start of defintion)" ) );
306            }
307            br.close();
308            return identifier;
309        }
310    
311        /**
312         * Creates a helpfull string of the given StreamTokenizer value.
313         *
314         * @param val
315         *            an int gotten from streamTokenizer.ttype.
316         * @return a human readable String.
317         */
318        private String getTokenizerSymbolToString( int val ) {
319            String result = new String( "" + val );
320            if ( val == StreamTokenizer.TT_EOF ) {
321                result = "End of file";
322            } else if ( val == StreamTokenizer.TT_EOL ) {
323                result = "End of line";
324            } else if ( val == StreamTokenizer.TT_NUMBER ) {
325                result = "A number with value: " + val;
326            }
327            return result;
328        }
329    
330        /**
331         * removes any strings in front of the last number.
332         *
333         * @param id
334         *            to be normalized.
335         * @return the number of the id, or id if ':' or '#' is not found.
336         */
337        private String getIDCode( String id ) {
338            if ( id == null || "".equals( id.trim() ) ) {
339                return id;
340            }
341            int count = id.lastIndexOf( ":" );
342            if ( count == -1 ) {
343                count = id.lastIndexOf( "#" );
344                if ( count == -1 ) {
345                    return id;
346                }
347            }
348            return id.substring( count + 1 );
349        }
350    
351        public Transformation getTransformation( CoordinateSystem sourceCRS, CoordinateSystem targetCRS ) {
352            LOG.logError( "Parsing of transformations is currently not supported for the Proj4 file resource." );
353            return null;
354        }
355    
356    }