001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.data.projection;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import org.openstreetmap.josm.data.Bounds;
007    import org.openstreetmap.josm.data.coor.LatLon;
008    import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
009    import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
010    import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
011    import org.openstreetmap.josm.data.projection.proj.ProjParameters;
012    
013    /**
014     * Lambert conic conform 4 zones using the French geodetic system NTF.
015     * This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy.
016     * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal)
017     *
018     * Source: http://professionnels.ign.fr/DISPLAY/000/526/700/5267002/transformation.pdf
019     * @author Pieren
020     */
021    public class Lambert extends AbstractProjection {
022    
023        /**
024         * Lambert I, II, III, and IV latitude origin
025         */
026        private static final double lat_0s[] = { 49.5, 46.8, 44.1, 42.165 };
027        
028        /**
029         * Lambert I, II, III, and IV latitude of first standard parallel
030         */
031        private static final double lat_1s[] = { 
032                convertDegreeMinuteSecond(48, 35, 54.682), 
033                convertDegreeMinuteSecond(45, 53, 56.108),
034                convertDegreeMinuteSecond(43, 11, 57.449),
035                convertDegreeMinuteSecond(41, 33, 37.396)};
036        
037        /**
038         * Lambert I, II, III, and IV latitude of second standard parallel
039         */
040        private static final double lat_2s[] = {
041                convertDegreeMinuteSecond(50, 23, 45.282),
042                convertDegreeMinuteSecond(47, 41, 45.652),
043                convertDegreeMinuteSecond(44, 59, 45.938),
044                convertDegreeMinuteSecond(42, 46, 3.588)};
045    
046        /**
047         * Lambert I, II, III, and IV false east
048         */
049        private static final double x_0s[] = { 600000.0, 600000.0, 600000.0, 234.358 };
050        
051        /**
052         * Lambert I, II, III, and IV false north
053         */
054        private static final double y_0s[] = { 200000.0, 200000.0, 200000.0, 185861.369 };
055        
056        /**
057         * France is divided in 4 Lambert projection zones (1,2,3 + 4th for Corsica)
058         */
059        public static final double cMaxLatZone1Radian = Math.toRadians(57 * 0.9);
060        public static final double cMinLatZone1Radian = Math.toRadians(46.1 * 0.9);// lowest latitude of Zone 4 (South Corsica)
061    
062        public static final double[][] zoneLimitsDegree = {
063            {Math.toDegrees(cMaxLatZone1Radian), (53.5 * 0.9)}, // Zone 1  (reference values in grad *0.9)
064            {(53.5 * 0.9), (50.5 * 0.9)}, // Zone 2
065            {(50.5 * 0.9), (47.0 * 0.9)}, // Zone 3
066            {(47.51963 * 0.9), Math.toDegrees(cMinLatZone1Radian)} // Zone 4
067        };
068    
069        public static final double cMinLonZonesRadian = Math.toRadians(-4.9074074074074059 * 0.9);
070    
071        public static final double cMaxLonZonesRadian = Math.toRadians(10.2 * 0.9);
072    
073        /**
074         *  Allow some extension beyond the theoretical limits
075         */
076        public static final double cMaxOverlappingZonesDegree = 1.5;
077    
078        public static final int DEFAULT_ZONE = 0;
079    
080        private int layoutZone;
081    
082        public Lambert() {
083            this(DEFAULT_ZONE);
084        }
085    
086        public Lambert(final int layoutZone) {
087            if (layoutZone < 0 || layoutZone >= 4)
088                throw new IllegalArgumentException();
089            this.layoutZone = layoutZone;
090            ellps = Ellipsoid.clarkeIGN;
091            datum = new NTV2Datum("ntf_rgf93Grid", null, ellps, NTV2GridShiftFileWrapper.ntf_rgf93);
092            x_0 = x_0s[layoutZone];
093            y_0 = y_0s[layoutZone];
094            lon_0 = 2.0 + 20.0 / 60 + 14.025 / 3600; // 0 grade Paris
095            if (proj == null) {
096                proj = new LambertConformalConic();
097            }
098            proj = new LambertConformalConic();
099            try {
100                proj.initialize(new ProjParameters() {{
101                    ellps = Lambert.this.ellps;
102                    lat_0 = lat_0s[layoutZone];
103                    lat_1 = lat_1s[layoutZone];
104                    lat_2 = lat_2s[layoutZone];
105                }});
106            } catch (ProjectionConfigurationException e) {
107                throw new RuntimeException(e);
108            }
109        }
110    
111        @Override
112        public String toString() {
113            return tr("Lambert 4 Zones (France)");
114        }
115    
116        @Override
117        public Integer getEpsgCode() {
118            return 27561+layoutZone;
119        }
120    
121        @Override
122        public int hashCode() {
123            return getClass().getName().hashCode()+layoutZone; // our only real variable
124        }
125    
126        @Override
127        public String getCacheDirectoryName() {
128            return "lambert";
129        }
130    
131        @Override
132        public Bounds getWorldBoundsLatLon()
133        {
134            Bounds b= new Bounds(
135                    new LatLon(Math.max(zoneLimitsDegree[layoutZone][1] - cMaxOverlappingZonesDegree, Math.toDegrees(cMinLatZone1Radian)), Math.toDegrees(cMinLonZonesRadian)),
136                    new LatLon(Math.min(zoneLimitsDegree[layoutZone][0] + cMaxOverlappingZonesDegree, Math.toDegrees(cMaxLatZone1Radian)), Math.toDegrees(cMaxLonZonesRadian)),
137                    false);
138            return b;
139        }
140    
141        public int getLayoutZone() {
142            return layoutZone;
143        }
144    
145    }