Programming with JViews Maps > Ellipsoid and Geodetic Datums > Map Projections > Creating a New Projection

This section shows how to extend the JViews Maps projection package with your own projections. It is based on an example that implements a simplified version of the Mercator projection.

Complete Code Example

The complete code for this example can be found in the following file:

<installdir>/jviews-maps81/codefragments/projection/srchtml/Mercator.java

This section covers the following topics:

Defining a New Projection Class

To define a new class, you must first import the projection library located in the package ilog.views.maps.projection.

The complete source code of the examples presented in the next sections can be found in the following file:

<installdir>/jviews-maps81/codefragments/projection/srchtml/Mercator.java

Your projection class must extend the class IlvProjection, which is the base class for all the projections in the package.

import ilog.views.maps.projection.*;
import ilog.views.maps.*;

class MercatorProjection
extends IlvProjection

Writing the Constructor

You must call the constructor of the superclass IlvProjection.

MercatorProjection()
{
   super(true, true, IlvProjection.CONFORMAL);
}

This constructor takes the following three arguments:

Writing the Forward Projection

Before writing the forward() method for the Mercator projection, you must be familiar with the IlvProjection.forward() method.

The IlvProjection.forward Method

The IlvProjection.forward() public method is called by the user to project data. This method prepares data for projection computation and scales it appropriately. It then redirects the calls to either one of the eforward or sForward protected methods which are defined in the projection subclass (the Mercator class in our example). In most cases, the forward method should not be overridden.

The IlvProjection.forward() method does the following:

Projecting Data from a Sphere

The sforward() protected method implements the projection of a sphere.

Since the appropriate scaling is actually carried out by the method IlvProjection.forward(), the sForward() method always assumes that the radius of the sphere is 1.

In our example, the Mercator projection is the projection of a sphere onto a cylinder that is tangent to the equator. The x coordinate is equal to the longitude because it is assumed that the radius of the sphere is 1, and longitude is expressed in radians. In this case, you do not need to change the x value of ll.

Because the Mercator projection cannot show regions near the poles, the exception IlvToleranceConditionException is thrown if the latitude is too close to PI/2.

Apply the equation to compute the y coordinate of the projected data.

protected void sForward(IlvCoordinate ll)
  throws IlvToleranceConditionException
{
  if (Math.abs(Math.abs(ll.y) - Math.PI / 2D) <= 1e-10D)
     throw new IlvToleranceConditionException();
 
  ll.y = Math.log(Math.tan(Math.PI / 4D + .5D * ll.y));
}

Projecting Data from an Ellipsoid

The eforward() protected method is called by the IlvProjection.forward() method if data is projected from a nonspherical ellipsoid.

It is not necessary for you to implement the eForward() method for your projection. If you are projecting data from a nonspherical ellipsoid and if the projection you are using does not support this kind of ellipsoid, the forward() method will throw the exception IlvUnsupportedProjectionFeature. In this case, you can use any spherical ellipsoid or create an equivalent sphere using the appropriate conversion methods of the class IlvEllipsoid.

The eForward() method is slightly more complex than the sForward() method although their formulas are equivalent if getEllipsoid().getE() returns 0.

protected void eForward(IlvCoordinate ll)
   throws IlvToleranceConditionException
{
  if (Math.abs(Math.abs(ll.y) - Math.PI / 2D) <= 1e-10D) 
     throw new IlvToleranceConditionException();

  double e = Math.sqrt(getEllipsoid().getES());

  double sinphi = e * Math.sin(ll.y);
  ll.y = Math.tan (.5D * (Math.PI/2D - ll.y)) /
         Math.pow((1D - sinphi) / (1D + sinphi), 
                   .5D * e);
  ll.y = -Math.log(ll.y);
}

Writing the Inverse Projection

Before writing the inverse() method for the Mercator projection, you should be familiar with the IlvProjection.inverse() method.

The IlvProjection.inverse Method

The inverse() method prepares the data for inversion and processes it for the appropriate offset. In most cases, you should not have to override the IlvProjection.inverse() method.

This method does the following:

Inverse Projection onto a Sphere

The inverse projection onto a sphere is performed via the sInverse() method.

It is not necessary for you to implement the sInverse() method. If you call the IlvProjection.inverse() method for a projection that does not support the inverse() method, the exception IlvUnsupportedProjectionFeature will be thrown.

The sInverse() method can throw the exception IlvToleranceConditionException like all the forward methods. But since the inverse equation of the Mercator projection is defined for all the possible values, this method does not throw any exceptions.

As seen earlier with the sForward() method, the projection does not modify the x value, therefore, the inverse equation is applied only to the y value.

protected void sInverse(IlvCoordinate xy)
{
  xy.y = Math.PI/2D - 2D * Math.atan(Math.exp(-xy.y));
}

Inverse Projection onto an Ellipsoid

The inverse projection onto an ellipsoid is performed via the eInverse() method.

This method assumes that the value of the semi-major axis of the ellipsoid is 1.

In the particular case of the Mercator projection, the implementation of this method is more complex for an ellipsoid than for a sphere. It requires iterations and might fail, since there is no simple analytical inverse equation of the Mercator projection from a nonspherical ellipsoid.

protected void eInverse(IlvCoordinate xy)
  throws IlvToleranceConditionException
{
  double ts = Math.exp(- xy.y);
  double e = Math.sqrt(getEllipsoid().getES());
  double eccnth = .5D * e;

  double Phi = Math.PI/2D - 2D * Math.atan(ts);

  int i = 15;
  double dphi;
  do {
    double con = e * Math.sin (Phi);
    dphi = Math.PI/2D - 2D * Math.atan(ts * Math.pow((1D - con) /
           (1D + con), eccnth)) - Phi;
    Phi += dphi;
  } while ((Math.abs(dphi) > 1e-10D) && (--i != 0));
  if (i <= 0)
    throw new IlvToleranceConditionException("non-convergent inverse phi2");
  xy.y = Phi;
}