// 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.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
/**
* This class provides the two methods latlon2eastNorth
and eastNorth2latlon
* converting the JOSM LatLon coordinates in WGS84 system (GPS) to and from East North values in
* the projection Lambert conic conform 4 zones using the French geodetic system NTF.
* This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy.
* (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal)
* @author Pieren
*/
public class Lambert implements Projection, ProjectionSubPrefs {
/**
* Lambert I, II, III, and IV projection exponents
*/
public static final double n[] = { 0.7604059656, 0.7289686274, 0.6959127966, 0.6712679322 };
/**
* Lambert I, II, III, and IV projection constants
*/
public static final double c[] = { 11603796.98, 11745793.39, 11947992.52, 12136281.99 };
/**
* Lambert I, II, III, and IV false east
*/
public static final double Xs[] = { 600000.0, 600000.0, 600000.0, 234.358 };
/**
* Lambert I, II, III, and IV false north
*/
public static final double Ys[] = { 5657616.674, 6199695.768, 6791905.085, 7239161.542 };
/**
* 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-11;
/**
* France is divided in 4 Lambert projection zones (1,2,3 + 4th for Corsica)
*/
public static final double cMaxLatZone1Radian = Math.toRadians(57 * 0.9);
public static final double cMinLatZone1Radian = Math.toRadians(46.1 * 0.9);// lowest latitude of Zone 4 (South Corsica)
public static final double zoneLimitsDegree[][] = {
{Math.toDegrees(cMaxLatZone1Radian), (53.5 * 0.9)}, // Zone 1 (reference values in grad *0.9)
{(53.5 * 0.9), (50.5 * 0.9)}, // Zone 2
{(50.5 * 0.9), (47.0 * 0.9)}, // Zone 3
{(47.51963 * 0.9), Math.toDegrees(cMinLatZone1Radian)} // Zone 4
};
public static final double cMinLonZonesRadian = Math.toRadians(-4.9074074074074059 * 0.9);
public static final double cMaxLonZonesRadian = Math.toRadians(10.2 * 0.9);
/**
* Allow some extension beyond the theoretical limits
*/
public static final double cMaxOverlappingZonesDegree = 1.5;
public static final int DEFAULT_ZONE = 0;
private int layoutZone = DEFAULT_ZONE;
private static NTV2GridShiftFile ntf_rgf93Grid = null;
public static NTV2GridShiftFile getNtf_rgf93Grid() {
return ntf_rgf93Grid;
}
public Lambert() {
if (ntf_rgf93Grid == null) {
try {
String gridFileName = "ntf_r93_b.gsb";
InputStream is = Main.class.getResourceAsStream("/data/"+gridFileName);
if (is == null) {
System.err.println(tr("Warning: failed to open input stream for resource ''/data/{0}''. Cannot load NTF<->RGF93 grid", gridFileName));
return;
}
ntf_rgf93Grid = new NTV2GridShiftFile();
ntf_rgf93Grid.loadGridShiftFile(is, false);
//System.out.println("NTF<->RGF93 grid loaded.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @param p WGS84 lat/lon (ellipsoid GRS80) (in degree)
* @return eastnorth projection in Lambert Zone (ellipsoid Clark)
* @throws IOException
*/
public EastNorth latlon2eastNorth(LatLon p) {
// translate ellipsoid GRS80 (WGS83) => Clark
LatLon geo = WGS84_to_NTF(p);
double lt = Math.toRadians(geo.lat()); // in radian
double lg = Math.toRadians(geo.lon());
// check if longitude and latitude are inside the French Lambert zones
if (lt >= cMinLatZone1Radian && lt <= cMaxLatZone1Radian && lg >= cMinLonZonesRadian && lg <= cMaxLonZonesRadian)
return ConicProjection(lt, lg, Xs[layoutZone], Ys[layoutZone], c[layoutZone], n[layoutZone]);
return ConicProjection(lt, lg, Xs[0], Ys[0], c[0], n[0]);
}
public LatLon eastNorth2latlon(EastNorth p) {
LatLon geo;
geo = Geographic(p, Xs[layoutZone], Ys[layoutZone], c[layoutZone], n[layoutZone]);
// translate geodetic system from NTF (ellipsoid Clark) to RGF93/WGS84 (ellipsoid GRS80)
return NTF_to_WGS84(geo);
}
@Override public String toString() {
return tr("Lambert 4 Zones (France)");
}
public String toCode() {
return "EPSG:"+(27561+layoutZone);
}
@Override
public int hashCode() {
return getClass().getName().hashCode()+layoutZone; // our only real variable
}
public String getCacheDirectoryName() {
return "lambert";
}
/**
* Initializes from geographic coordinates. Note that reference ellipsoid
* used by Lambert is the Clark ellipsoid.
*
* @param lat latitude in grad
* @param lon longitude in grad
* @param Xs false east (coordinate system origin) in meters
* @param Ys false north (coordinate system origin) in meters
* @param c projection constant
* @param n projection exponent
* @return EastNorth projected coordinates in meter
*/
private EastNorth ConicProjection(double lat, double lon, double Xs, double Ys, double c, double n) {
double eslt = Ellipsoid.clarke.e * Math.sin(lat);
double l = Math.log(Math.tan(Math.PI / 4.0 + (lat / 2.0))
* Math.pow((1.0 - eslt) / (1.0 + eslt), Ellipsoid.clarke.e / 2.0));
double east = Xs + c * Math.exp(-n * l) * Math.sin(n * (lon - lg0));
double north = Ys - c * Math.exp(-n * l) * Math.cos(n * (lon - lg0));
return new EastNorth(east, north);
}
/**
* Initializes from projected coordinates (conic projection). Note that
* reference ellipsoid used by Lambert is Clark
*
* @param eastNorth projected coordinates pair in meters
* @param Xs false east (coordinate system origin) in meters
* @param Ys false north (coordinate system origin) in meters
* @param c projection constant
* @param n projection exponent
* @return LatLon in degree
*/
private LatLon Geographic(EastNorth eastNorth, double Xs, double Ys, double c, double n) {
double dx = eastNorth.east() - Xs;
double dy = Ys - eastNorth.north();
double R = Math.sqrt(dx * dx + dy * dy);
double gamma = Math.atan(dx / dy);
double l = -1.0 / n * Math.log(Math.abs(R / c));
l = Math.exp(l);
double lon = lg0 + gamma / n;
double lat = 2.0 * Math.atan(l) - Math.PI / 2.0;
double delta = 1.0;
while (delta > epsilon) {
double eslt = Ellipsoid.clarke.e * Math.sin(lat);
double nlt = 2.0 * Math.atan(Math.pow((1.0 + eslt) / (1.0 - eslt), Ellipsoid.clarke.e / 2.0) * l) - Math.PI
/ 2.0;
delta = Math.abs(nlt - lat);
lat = nlt;
}
return new LatLon(Math.toDegrees(lat), Math.toDegrees(lon)); // in radian
}
/**
* Translate latitude/longitude in WGS84, (ellipsoid GRS80) to Lambert
* geographic, (ellipsoid Clark)
* @throws IOException
*/
private LatLon WGS84_to_NTF(LatLon wgs) {
NTV2GridShift gs = new NTV2GridShift(wgs);
if (ntf_rgf93Grid != null) {
ntf_rgf93Grid.gridShiftReverse(gs);
return new LatLon(wgs.lat()+gs.getLatShiftDegrees(), wgs.lon()+gs.getLonShiftPositiveEastDegrees());
}
return new LatLon(0,0);
}
private LatLon NTF_to_WGS84(LatLon ntf) {
NTV2GridShift gs = new NTV2GridShift(ntf);
if (ntf_rgf93Grid != null) {
ntf_rgf93Grid.gridShiftForward(gs);
return new LatLon(ntf.lat()+gs.getLatShiftDegrees(), ntf.lon()+gs.getLonShiftPositiveEastDegrees());
}
return new LatLon(0,0);
}
public Bounds getWorldBoundsLatLon()
{
Bounds b= new Bounds(
new LatLon(Math.max(zoneLimitsDegree[layoutZone][1] - cMaxOverlappingZonesDegree, Math.toDegrees(cMinLatZone1Radian)), Math.toDegrees(cMinLonZonesRadian)),
new LatLon(Math.min(zoneLimitsDegree[layoutZone][0] + cMaxOverlappingZonesDegree, Math.toDegrees(cMaxLatZone1Radian)), Math.toDegrees(cMaxLonZonesRadian)));
return b;
}
/**
* 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 int getLayoutZone() {
return layoutZone;
}
public static String[] lambert4zones = {
tr("{0} ({1} to {2} degrees)", 1,"51.30","48.15"),
tr("{0} ({1} to {2} degrees)", 2,"48.15","45.45"),
tr("{0} ({1} to {2} degrees)", 3,"45.45","42.76"),
tr("{0} (Corsica)", 4)
};
public void setupPreferencePanel(JPanel p) {
JComboBox prefcb = new JComboBox(lambert4zones);
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", "Departements_Lambert4Zones.png")), GBC.eol().fill(GBC.HORIZONTAL));
p.add(GBC.glue(1, 1), GBC.eol().fill(GBC.BOTH));
}
public Collection 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 args) {
layoutZone = DEFAULT_ZONE;
if (args != null) {
try {
for(String s : args)
{
layoutZone = Integer.parseInt(s)-1;
if(layoutZone < 0 || layoutZone > 3) {
layoutZone = DEFAULT_ZONE;
}
break;
}
} catch(NumberFormatException e) {}
}
}
public Collection getPreferencesFromCode(String code) {
if (code.startsWith("EPSG:2756") && code.length() == 9) {
try {
String zonestring = code.substring(9);
int zoneval = Integer.parseInt(zonestring);
if(zoneval >= 1 && zoneval <= 4)
return Collections.singleton(zonestring);
} catch(NumberFormatException e) {}
}
return null;
}
}