001    //$HeadURL: svn+ssh://developername@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/model/csct/cs/ConvenienceCSFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     lat/lon GmbH
007     http://www.lat-lon.de
008    
009     This library is free software; you can redistribute it and/or
010     modify it under the terms of the GNU Lesser General Public
011     License as published by the Free Software Foundation; either
012     version 2.1 of the License, or (at your option) any later version.
013    
014     This library is distributed in the hope that it will be useful,
015     but WITHOUT ANY WARRANTY; without even the implied warranty of
016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
017     Lesser General Public License for more details.
018    
019     You should have received a copy of the GNU Lesser General Public
020     License along with this library; if not, write to the Free Software
021     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
022    
023     Contact:
024    
025     Andreas Poth
026     lat/lon GmbH
027     Aennchenstr. 19
028     53115 Bonn
029     Germany
030     E-Mail: poth@lat-lon.de
031     
032     ---------------------------------------------------------------------------*/
033    package org.deegree.model.crs;
034    
035    import java.awt.geom.Point2D;
036    import java.net.URI;
037    import java.net.URL;
038    import java.util.HashMap;
039    
040    import org.deegree.framework.log.ILogger;
041    import org.deegree.framework.log.LoggerFactory;
042    import org.deegree.framework.util.BootLogger;
043    import org.deegree.framework.xml.NamespaceContext;
044    import org.deegree.framework.xml.XMLFragment;
045    import org.deegree.framework.xml.XMLParsingException;
046    import org.deegree.framework.xml.XMLTools;
047    import org.deegree.model.crs.CRSException;
048    import org.deegree.model.crs.CSAccess;
049    import org.deegree.model.csct.cs.AxisInfo;
050    import org.deegree.model.csct.cs.CoordinateSystem;
051    import org.deegree.model.csct.cs.CoordinateSystemFactory;
052    import org.deegree.model.csct.cs.Datum;
053    import org.deegree.model.csct.cs.DatumType;
054    import org.deegree.model.csct.cs.Ellipsoid;
055    import org.deegree.model.csct.cs.GeographicCoordinateSystem;
056    import org.deegree.model.csct.cs.HorizontalDatum;
057    import org.deegree.model.csct.cs.PrimeMeridian;
058    import org.deegree.model.csct.cs.ProjectedCoordinateSystem;
059    import org.deegree.model.csct.cs.Projection;
060    import org.deegree.model.csct.cs.WGS84ConversionInfo;
061    import org.deegree.model.csct.units.Unit;
062    import org.deegree.ogcbase.CommonNamespaces;
063    import org.w3c.dom.Element;
064    
065    /**
066     * 
067     * 
068     * @version $Revision: 1.1 $
069     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
070     */
071    public class DeegreeCSAccess implements CSAccess {
072    
073        private static final ILogger LOG = LoggerFactory.getLogger( DeegreeCSAccess.class );
074    
075        private static final String CRS_DEF = "crs.xml";
076    
077        private static NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
078        static {
079            try {
080                nsc.addNamespace( "crs", new URI( "http://www.deegree.org/crs" ) );
081            } catch ( Exception e ) {
082                BootLogger.logError( e.getMessage(), e );
083            }
084        }
085    
086        private CoordinateSystemFactory csFactory = CoordinateSystemFactory.getDefault();
087    
088        /**
089         * keys are names (i.e. "EPSG:4326"), values are CoordinateSystems
090         */
091        private HashMap<String, CoordinateSystem> systems = new HashMap<String, CoordinateSystem>( 300 );
092    
093        /**
094         * keys are names (i.e. "EPSG:7006"), values are Ellipsoids
095         */
096        private HashMap<String, Ellipsoid> ellipsoids = new HashMap<String, Ellipsoid>( 30 );
097    
098        /**
099         * keys are names (i.e. "EPSG:7006"), values are Ellipsoids
100         */
101        private HashMap<String, Datum> datums = new HashMap<String, Datum>( 30 );
102    
103        /**
104         * keys are names (i.e. "EPSG:1777"), values are WGS84Conversion object
105         */
106        private HashMap<String, WGS84ConversionInfo> toWGS84Conversions = new HashMap<String, WGS84ConversionInfo>( 30 );
107    
108        /*
109         * (non-Javadoc)
110         * 
111         * @see de.latlon.vodafone.crs.CSAccess#getCSByCode(java.lang.String, java.lang.String)
112         */
113        public synchronized CoordinateSystem getCSByCode( String code, String version )
114                                throws CRSException {
115            // normalize code
116            code = code.toUpperCase();
117    
118            // reading CRS from cache and return it if available
119            CoordinateSystem cs = systems.get( code.toUpperCase() );
120            if ( cs != null ) {
121                return cs;
122            }
123    
124            // read defintion of CRS with passed code from CRS defintion resource
125            // if no CRS with passed code is defined null will be returned
126            Element crsElement = readCRSDefinition( code );
127            if ( crsElement == null ) {
128                throw new CRSException( "requested CRS with code: " + code + " is not defined" );
129            }
130    
131            try {
132                createCRS( crsElement );
133            } catch ( XMLParsingException e ) {
134                LOG.logError( e.getMessage(), e );
135                throw new CRSException( e.getMessage(), e );
136            }
137    
138            return systems.get( code );
139        }
140    
141        /**
142         * creates
143         * 
144         * @see CoordinateSystem from the passed Element
145         * @param crsElement
146         * @throws CRSException
147         * @throws XMLParsingException
148         */
149        private void createCRS( Element crsElement )
150                                throws CRSException, XMLParsingException {
151    
152            String type = crsElement.getLocalName();
153            if ( "projectedReferenceSytemen".equals( type ) ) {
154                createProjectedCRS( crsElement );
155            } else if ( "geographicReferenceSytem".equals( type ) ) {
156                createGeographicCRS( crsElement );
157            } else {
158                throw new CRSException( "not supoorted CRS type: " + type );
159            }
160    
161        }
162    
163        /**
164         * 
165         * @param crsElement
166         * @throws XMLParsingException
167         * @throws CRSException
168         */
169        private void createGeographicCRS( Element crsElement )
170                                throws XMLParsingException, CRSException {
171    
172            String code = XMLTools.getRequiredNodeAsString( crsElement, "crs:code/text()", nsc );
173    
174            GeographicCoordinateSystem cs = null;
175            if ( "EPSG:4326".equals( code ) || "WGS84".equals( code ) ) {
176                // use standard parameters because no conversion is required
177                cs = csFactory.createGeographicCoordinateSystem( code, Unit.DEGREE, HorizontalDatum.WGS84,
178                                                                 PrimeMeridian.GREENWICH, AxisInfo.LONGITUDE,
179                                                                 AxisInfo.LATITUDE );
180            } else {
181    
182                // try getting WGS84 conversion parameters from cache. If no conversion parameters with
183                // required code is available in cache: read parameter and create an object storing
184                // informations for converting current geographic reference system (better: its
185                // underlying geogr. datum) to WGS84
186                String toWGS84Code = XMLTools.getNodeAsString( crsElement, "crs:wgs84ConversionInfo/text()", nsc, null );
187                WGS84ConversionInfo convInfo = toWGS84Conversions.get( toWGS84Code );
188                if ( convInfo == null ) {
189                    String xPath = "/crs:definitons/crs:transformation[crs:code = '" + toWGS84Code + "']";
190                    Element toWGS84Element = XMLTools.getRequiredElement( crsElement, xPath, nsc );
191                    createWGS84ConversionInfo( toWGS84Element );
192                    convInfo = toWGS84Conversions.get( toWGS84Code );
193                }
194    
195                // read code of used datum
196                String datumCode = XMLTools.getRequiredNodeAsString( crsElement, "crs:usedDatum/text()", nsc );
197                HorizontalDatum datum = (HorizontalDatum)datums.get( code );
198                if ( datum == null ) {
199                    String xPath = "/crs:definitons/crs:datum[crs:code = '" + datumCode + "']";
200                    Element datumElement = XMLTools.getRequiredElement( crsElement, xPath, nsc );
201                    createDatum( datumElement, convInfo );
202                    datum = (HorizontalDatum)datums.get( datumCode );
203                }
204    
205                String axisOrder = XMLTools.getNodeAsString( crsElement, "crs:axisOrder/text()", nsc, "XY" );
206                // consider axisOrder creating reference system
207                if ( "XY".equals( axisOrder ) ) {
208                    cs = csFactory.createGeographicCoordinateSystem( code, Unit.DEGREE, datum, PrimeMeridian.GREENWICH,
209                                                                     AxisInfo.LONGITUDE, AxisInfo.LATITUDE );                
210                } else {
211                    cs = csFactory.createGeographicCoordinateSystem( code, Unit.DEGREE, datum, PrimeMeridian.GREENWICH,
212                                                                     AxisInfo.LATITUDE, AxisInfo.LONGITUDE );
213                }
214    
215            }
216            systems.put( code, cs );
217    
218        }
219    
220        /**
221         * 
222         * @param datumElement
223         * @param convInfo
224         * @throws XMLParsingException
225         * @throws CRSException
226         */
227        private void createDatum( Element datumElement, WGS84ConversionInfo convInfo )
228                                throws XMLParsingException, CRSException {
229            
230            String code = XMLTools.getRequiredNodeAsString( datumElement, "crs:code/text()", nsc );
231            String type = XMLTools.getRequiredNodeAsString( datumElement, "crs:datumType/text()", nsc );
232            
233            // read code of used ellipsoid
234            String ellCode = XMLTools.getRequiredNodeAsString( datumElement, "crs:usedEllipsoid/text()", nsc );
235            // try getting elliposid from cache. If no ellipsoid with required code
236            // is available in cache: create it
237            Ellipsoid ellipsoid = ellipsoids.get( ellCode );
238            if ( ellipsoid == null ) {
239                String xPath = "/crs:definitons/crs:ellipsoid[crs:code = '" + ellCode + "']";
240                Element ellipsoidElement = XMLTools.getRequiredElement( datumElement, xPath, nsc );
241                createEllipsoid( ellipsoidElement );
242                ellipsoid = ellipsoids.get( ellCode );
243            }
244            // at the moment just horizontal geogr. datum are supported
245            if ( type.toLowerCase().equals( "geodetic" ) ) {
246                datums.put( code, new HorizontalDatum( code, DatumType.CLASSIC, ellipsoid, convInfo ) );            
247            } else {
248                throw new CRSException( "not supported datum type: " + type );
249            }
250        }
251    
252        /**
253         * 
254         * @param toWGS84Element
255         * @throws XMLParsingException
256         */
257        private void createWGS84ConversionInfo( Element toWGS84Element )
258                                throws XMLParsingException {
259            String code = XMLTools.getRequiredNodeAsString( toWGS84Element, "crs:code/text()", nsc );
260            WGS84ConversionInfo convInfo = new WGS84ConversionInfo();
261            convInfo.dx = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:xAxisTranslation/text()", nsc );
262            convInfo.dy = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:yAxisTranslation/text()", nsc );
263            convInfo.dz = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:zAxisTranslation/text()", nsc );
264            convInfo.ex = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:xAxisRotation/text()", nsc );
265            convInfo.ey = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:yAxisRotation/text()", nsc );
266            convInfo.ez = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:zAxisRotation/text()", nsc );
267            convInfo.ppm = XMLTools.getRequiredNodeAsDouble( toWGS84Element, "crs:scaleDifference/text()", nsc );
268            toWGS84Conversions.put( code, convInfo );
269        }
270    
271        /**
272         * 
273         * @param ellipsoidElement
274         * @throws XMLParsingException
275         * @throws CRSException
276         */
277        private void createEllipsoid( Element ellipsoidElement )
278                                throws XMLParsingException, CRSException {
279    
280            String code = XMLTools.getRequiredNodeAsString( ellipsoidElement, "crs:code/text()", nsc );
281            double semiMajorAxis = XMLTools.getRequiredNodeAsDouble( ellipsoidElement, "crs:semiMajorAxis/text()", nsc );
282            double inverseFlatting = XMLTools.getRequiredNodeAsDouble( ellipsoidElement, "crs:inverseFlatting/text()", nsc );
283            Unit unit = createUnit( ellipsoidElement );
284    
285            Ellipsoid ellipsoid = Ellipsoid.createFlattenedSphere( code, semiMajorAxis, inverseFlatting, unit );
286            ellipsoids.put( code, ellipsoid );
287        }
288    
289        /**
290         * 
291         * @param ellipsoidElement
292         * @return
293         * @throws XMLParsingException
294         * @throws CRSException
295         */
296        private Unit createUnit( Element rootElement )
297                                throws XMLParsingException, CRSException {
298            String units = XMLTools.getRequiredNodeAsString( rootElement, "crs:units/text()", nsc );
299    
300            Unit unit = null;
301            if ( "metre".equals( units ) || "meter".equals( units ) ) {
302                unit = Unit.METRE;
303            } else if ( "degree".equals( units ) ) {
304                unit = Unit.DEGREE;
305            } else if ( "britishyard".equals( units ) ) {
306                unit = Unit.BRITISHYARD;
307            } else {
308                throw new CRSException( "unknown unit '" + units + "'" );
309            }
310            return unit;
311        }
312    
313        /**
314         * at the moment just TransverseMercator projection is supported
315         * 
316         * @param crsElement
317         * @throws XMLParsingException
318         * @throws CRSException
319         */
320        private void createProjectedCRS( Element crsElement )
321                                throws XMLParsingException, CRSException {
322            String xPath = "crs:projectionType/text()";
323            String projection = XMLTools.getRequiredNodeAsString( crsElement, xPath, nsc );
324            if ( !"TransverseMercator".equals( projection ) ) {
325                throw new CRSException( "not supported projection: " + projection );
326            }
327            if ( "TransverseMercator".equals( projection ) ) {
328                createTransverseMercatorCRS( crsElement );
329            }
330        }
331    
332        /**
333         * 
334         * @param crsElement
335         * @throws XMLParsingException
336         * @throws CRSException
337         */
338        private void createTransverseMercatorCRS( Element crsElement )
339                                throws XMLParsingException, CRSException {
340            String code = XMLTools.getRequiredNodeAsString( crsElement, "crs:code/text()", nsc );
341            String name = XMLTools.getNodeAsString( crsElement, "crs:name/text()", nsc, "-" );
342    
343            // try getting geographic CRS parameters from cache. If no geographic CRS with
344            // required code is available in cache: read parameter and create it
345            String geogrCRSCode = XMLTools.getRequiredNodeAsString( crsElement, "crs:geographicReferenceSytem/text()", nsc );
346            GeographicCoordinateSystem geogrCRS = (GeographicCoordinateSystem) systems.get( geogrCRSCode );
347            if ( geogrCRS == null ) {
348                String xPath = "/crs:definitons/crs:geographicReferenceSytem[crs:code = '" + geogrCRSCode + "']";
349                Element geogrCRSElement = XMLTools.getRequiredElement( crsElement, xPath, nsc );
350                createGeographicCRS( geogrCRSElement );
351            }
352            geogrCRS = (GeographicCoordinateSystem) systems.get( geogrCRSCode );
353            Ellipsoid ellipsoid = geogrCRS.getHorizontalDatum().getEllipsoid();
354    
355            // read projection parameters and create a projection object
356            double latono = XMLTools.getRequiredNodeAsDouble( crsElement, "crs:latitudeOfNaturalOrigin/text()", nsc );
357            double lonono = XMLTools.getRequiredNodeAsDouble( crsElement, "crs:longitudeOfNaturalOrigin/text()", nsc );
358            double scaleFactor = XMLTools.getRequiredNodeAsDouble( crsElement, "crs:scaleFactor/text()", nsc );
359            double falseEasting = XMLTools.getRequiredNodeAsDouble( crsElement, "crs:falseEasting/text()", nsc );
360            double falseNorthing = XMLTools.getRequiredNodeAsDouble( crsElement, "crs:falseNorthing/text()", nsc );
361            Unit unit = createUnit( crsElement );
362    
363            Projection projection = csFactory.createProjection( name, "Transverse_Mercator", ellipsoid,
364                                                                new Point2D.Double( lonono, latono ),
365                                                                new Point2D.Double( falseEasting, falseNorthing ),
366                                                                scaleFactor );
367    
368            // create projected CRS considering axisOrder
369            String axisOrder = XMLTools.getNodeAsString( crsElement, "crs:axisOrder/text()", nsc, "XY" );
370            ProjectedCoordinateSystem cs = null;
371            if ( "XY".equals( axisOrder ) ) {
372                cs = csFactory.createProjectedCoordinateSystem( code, geogrCRS, projection, unit, AxisInfo.X, AxisInfo.Y );
373            } else {
374                cs = csFactory.createProjectedCoordinateSystem( code, geogrCRS, projection, unit, AxisInfo.Y, AxisInfo.X );
375            }
376            systems.put( code, cs );
377        }
378    
379        /**
380         * reads a CRS defintion having passed code from CRS definition document
381         * 
382         * @param code
383         * @return
384         * @throws CRSException
385         */
386        private Element readCRSDefinition( String code )
387                                throws CRSException {
388    
389            XMLFragment xml = null;
390            try {
391                URL url = DeegreeCSAccess.class.getResource( "/crs.xml" );
392                xml = new XMLFragment( url );
393            } catch ( Exception e ) {
394                URL url = DeegreeCSAccess.class.getResource( CRS_DEF );
395                try {
396                    xml = new XMLFragment( url );
397                } catch ( Exception ee ) {
398                    LOG.logError( ee.getMessage(), ee );
399                    throw new CRSException( "CRS definition document could not be loaded" );
400                }
401            }
402    
403            Element crsElement;
404            try {
405                String xPath = "/crs:definitons/*[crs:code = '" + code + "']";
406                crsElement = XMLTools.getRequiredElement( xml.getRootElement(), xPath, nsc );
407            } catch ( XMLParsingException e ) {
408                LOG.logError( e.getMessage(), e );
409                throw new CRSException( "CRS definition could not be read from CRS definition document" );
410            }
411            return crsElement;
412        }
413    
414    }