//License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.projection;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.GridBagLayout;
import java.util.Collection;
import java.util.Collections;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.projection.Projection;
import org.openstreetmap.josm.data.projection.Ellipsoid;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
/**
* This class implements the Lambert Conic Conform 9 Zones projection as specified by the IGN
* in this document http://professionnels.ign.fr/DISPLAY/000/526/700/5267002/transformation.pdf
* @author Pieren
*
*/
public class LambertCC9Zones implements Projection, ProjectionSubPrefs {
/**
* Lambert 9 zones projection exponents
*/
public static final double n[] = { 0.6691500006885269, 0.682018118346418, 0.6946784863203991, 0.7071272481559119,
0.7193606118567315, 0.7313748510399917, 0.7431663060711892, 0.7547313851789208, 0.7660665655489937};
/**
* Lambert 9 zones projection constants
*/
public static final double c[] = { 1.215363305807804E7, 1.2050261119223533E7, 1.195716926884592E7, 1.18737533925172E7,
1.1799460698022118E7, 1.17337838820243E7, 1.16762559948139E7, 1.1626445901183508E7, 1.1583954251630554E7};
/**
* Lambert 9 zones false east
*/
public static final double Xs = 1700000;
/**
* Lambert 9 zones false north
*/
public static final double Ys[] = { 8293467.503439436, 9049604.665107645, 9814691.693461388, 1.0588107871787189E7,
1.1369285637569271E7, 1.2157704903382052E7, 1.2952888086405803E7, 1.3754395745267643E7, 1.4561822739114787E7};
/**
* Lambert I, II, III, and IV longitudinal offset to Greenwich meridian
*/
public static final double lg0 = 0.04079234433198; // 2deg20'14.025"
/**
* precision in iterative schema
*/
public static final double epsilon = 1e-12;
/**
* France is divided in 9 Lambert projection zones, CC42 to CC50.
*/
public static final double cMaxLatZonesRadian = Math.toRadians(51.1);
public static final double cMinLatZonesDegree = 41.0;
public static final double cMinLatZonesRadian = Math.toRadians(cMinLatZonesDegree);
public static final double cMinLonZonesRadian = Math.toRadians(-5.0);
public static final double cMaxLonZonesRadian = Math.toRadians(10.2);
public static final double lambda0 = Math.toRadians(3);
public static final double e = Ellipsoid.GRS80.e; // but in doc=0.08181919112
public static final double e2 =Ellipsoid.GRS80.e2;
public static final double a = Ellipsoid.GRS80.a;
public static final double cMaxOverlappingZones = 1.5;
public static final int DEFAULT_ZONE = 0;
private static int layoutZone = DEFAULT_ZONE;
private double L(double phi, double e) {
double sinphi = Math.sin(phi);
return (0.5*Math.log((1+sinphi)/(1-sinphi))) - e/2*Math.log((1+e*sinphi)/(1-e*sinphi));
}
/**
* @param p WGS84 lat/lon (ellipsoid GRS80) (in degree)
* @return eastnorth projection in Lambert Zone (ellipsoid Clark)
*/
public EastNorth latlon2eastNorth(LatLon p) {
double lt = Math.toRadians(p.lat());
double lg = Math.toRadians(p.lon());
if (lt >= cMinLatZonesRadian && lt <= cMaxLatZonesRadian && lg >= cMinLonZonesRadian && lg <= cMaxLonZonesRadian)
return ConicProjection(lt, lg, layoutZone);
return ConicProjection(lt, lg, 0);
}
/**
*
* @param lat latitude in grad
* @param lon longitude in grad
* @param nz Lambert CC zone number (from 1 to 9) - 1 !
* @return EastNorth projected coordinates in meter
*/
private EastNorth ConicProjection(double lat, double lon, int nz) {
double R = c[nz]*Math.exp(-n[nz]*L(lat,e));
double gamma = n[nz]*(lon-lambda0);
double X = Xs + R*Math.sin(gamma);
double Y = Ys[nz] + -R*Math.cos(gamma);
return new EastNorth(X, Y);
}
public LatLon eastNorth2latlon(EastNorth p) {
return Geographic(p, layoutZone);
}
private LatLon Geographic(EastNorth ea, int nz) {
double R = Math.sqrt(Math.pow(ea.getX()-Xs,2)+Math.pow(ea.getY()-Ys[nz], 2));
double gamma = Math.atan((ea.getX()-Xs)/(Ys[nz]-ea.getY()));
double lon = lambda0+gamma/n[nz];
double latIso = (-1/n[nz])*Math.log(Math.abs(R/c[nz]));
double lat = Ellipsoid.GRS80.latitude(latIso, e, epsilon);
return new LatLon(Math.toDegrees(lat), Math.toDegrees(lon));
}
@Override public String toString() {
return tr("Lambert CC9 Zone (France)");
}
public static int north2ZoneNumber(double north) {
int nz = (int)(north /1000000) - 1;
if (nz < 0) return 0;
else if (nz > 8) return 8;
else return nz;
}
public String toCode() {
return "EPSG:"+(3942+layoutZone); //CC42 is EPSG:3942 (up to EPSG:3950 for CC50)
}
@Override
public int hashCode() {
return getClass().getName().hashCode()+layoutZone; // our only real variable
}
public String getCacheDirectoryName() {
return "lambert";
}
/**
* Returns the default zoom scale in pixel per degree ({@see #NavigatableComponent#scale}))
*/
public double getDefaultZoomInPPD() {
// this will set the map scaler to about 1000 m (in default scale, 1 pixel will be 10 meters)
return 10.0;
}
public Bounds getWorldBoundsLatLon()
{
double medLatZone = cMinLatZonesDegree + (layoutZone+1);
return new Bounds(
new LatLon(medLatZone - 1.0 - cMaxOverlappingZones, -4.9),
new LatLon(medLatZone + 1.0 + cMaxOverlappingZones, 10.2));
}
public int getLayoutZone() {
return layoutZone;
}
private static String[] lambert9zones = {
tr("{0} ({1} to {2} degrees)", 1,41,43),
tr("{0} ({1} to {2} degrees)", 2,42,44),
tr("{0} ({1} to {2} degrees)", 3,43,45),
tr("{0} ({1} to {2} degrees)", 4,44,46),
tr("{0} ({1} to {2} degrees)", 5,45,47),
tr("{0} ({1} to {2} degrees)", 6,46,48),
tr("{0} ({1} to {2} degrees)", 7,47,49),
tr("{0} ({1} to {2} degrees)", 8,48,50),
tr("{0} ({1} to {2} degrees)", 9,49,51)
};
public void setupPreferencePanel(JPanel p) {
JComboBox prefcb = new JComboBox(lambert9zones);
prefcb.setSelectedIndex(layoutZone);
p.setLayout(new GridBagLayout());
p.add(new JLabel(tr("Lambert CC Zone")), GBC.std().insets(5,5,0,5));
p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL));
/* Note: we use component position 2 below to find this again */
p.add(prefcb, GBC.eop().fill(GBC.HORIZONTAL));
p.add(new JLabel(ImageProvider.get("data/projection", "LambertCC9Zones.png")), GBC.eol().fill(GBC.HORIZONTAL));
p.add(GBC.glue(1, 1), GBC.eol().fill(GBC.BOTH));
}
public Collection<String> getPreferences(JPanel p) {
Object prefcb = p.getComponent(2);
if(!(prefcb instanceof JComboBox))
return null;
layoutZone = ((JComboBox)prefcb).getSelectedIndex();
return Collections.singleton(Integer.toString(layoutZone+1));
}
public void setPreferences(Collection<String> args)
{
layoutZone = DEFAULT_ZONE;
if (args != null) {
try {
for(String s : args)
{
layoutZone = Integer.parseInt(s)-1;
if(layoutZone < 0 || layoutZone > 8) {
layoutZone = DEFAULT_ZONE;
}
break;
}
} catch(NumberFormatException e) {}
}
}
public Collection<String> getPreferencesFromCode(String code)
{
//zone 1=CC42=EPSG:3942 up to zone 9=CC50=EPSG:3950
if (code.startsWith("EPSG:39") && code.length() == 9) {
try {
String zonestring = code.substring(5,4);
int zoneval = Integer.parseInt(zonestring)-3942;
if(zoneval >= 0 && zoneval <= 8)
return Collections.singleton(zonestring);
} catch(NumberFormatException e) {}
}
return null;
}
}