/** 
 * Copyright 1999 - 2002 Henry Bottomley (henry.bottomley@btinternet.com)
 * Created December 1999
 * Revised with more projections and more options January 2000 
 * and different image storage and more projections September 2002
 *
 * Uses an idea from Chapter 11 of Java Game Programming for Dummies 1998 IDG Books 
 * related to the capture and scaling of raster images
 *
 * Further work needed:
 *  protection against calculations which are either too precise or not precise enough
 *  more choice of projections
 *  improve explanation of choices
 *  optimisation and speed
 *  pick up parameter from HTML
 */

import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.lang.*;

public class MapProjections extends Applet 
    {
        
    protected Image     img;
    protected int[]     pixels;
    protected int       startx, starty, endx, endy, imgWidth, imgHeight, imgTotal;
    protected double    eastVal, northVal, rotVal;
    protected String    eastText, northText, rotText;
    protected String    imgName;
    protected String    proJection;
    protected String    projCentre;
    protected Choice    centreChoice = new Choice(); 
    protected Choice    projChoice = new Choice(); 
    protected TextField eastBox = new TextField(3);
    protected TextField northBox = new TextField(3);
    protected TextField rotBox = new TextField(3);
    protected Button    uChoose = new Button("You choose");
    static final double pi = Math.PI; 
    static final double pi180 = pi/180.0d; 
    static final int    spaceControl = 45; // space at top for controls
        
        
    public void init() 
        {
        
        projChoice.addItem("Longitude Latitude");
        projChoice.addItem("Mercator");
        projChoice.addItem("Cylindrical Equal Area");
        projChoice.addItem("Mollweide");
        projChoice.addItem("Sinusoidal");
        projChoice.addItem("Baar equal area");
        projChoice.addItem("Azimuthal Equal Area");
        projChoice.addItem("Azimuthal Distance");
        projChoice.addItem("Azimuthal Orthographic");
        projChoice.addItem("Azimuthal Stereographic");
        projChoice.addItem("Azimuthal Gnomonic");
        projChoice.addItem("Conic Distance (fat)");
        projChoice.addItem("Conic Distance (thin)");
        projChoice.addItem("Heart Equal Area");
        projChoice.addItem("Bonne");
        projChoice.addItem("Triangle Equal Area");
        add(projChoice);
        proJection="Longitude Latitude";
        
        centreChoice.addItem("Standard");
        centreChoice.addItem("North pole");
        centreChoice.addItem("South pole");
        centreChoice.addItem("Pacific");
        centreChoice.addItem("Random");
        add(centreChoice);
        projCentre="Standard";
        
        add (eastBox); 
        add (northBox); 
        add (rotBox);
        eastVal=0.0d;
        northVal=0.0d;
        rotVal=0.0d;
        
        add(uChoose);  
        
        imgName = (String) getParameter("basicimg");// Does not seem to work 
        if (imgName==null) { imgName="world.gif"; } // note image is "Longitude Latitude" projection 

        MediaTracker tracker = new MediaTracker(this);
        img = getImage(getCodeBase(), imgName );
        tracker.addImage(img, 0);
        try { tracker.waitForAll(); }
        catch (InterruptedException e) { }
        endx = imgWidth = img.getWidth(null);  
        endy = imgHeight = img.getHeight(null);
        imgTotal = imgWidth * imgHeight;
        // Extract pixel data using PixelGrabber
        pixels = new int[imgTotal];   //pixels is array with size=number of pixels in original image
        PixelGrabber pg = new PixelGrabber(img, 0, 0, imgWidth, imgHeight, pixels, 0, imgWidth);
        try { pg.grabPixels(); }
        catch (InterruptedException e) { }
            
        } // end of init method    
        
        
    public void paint (Graphics g) 
        {
        int[] drawMap = newmap(pixels,                      // image  to project
                               imgWidth, imgHeight,         // image  width and height
                               endx, endy,                  // display width and height
                               eastVal, northVal, rotVal,   // centre of projection
                               proJection);                 // name of projection
        Image sImg = createImage(new MemoryImageSource(endx, endy, drawMap, 0, endx));
        eastVal  -= 2.0d*pi*Math.rint(0.5d*eastVal/pi);         //force between pi and -pi 
        northVal -= 2.0d*pi*Math.rint(0.5d*northVal/pi);        //force between pi and -pi 
        rotVal   -= 2.0d*pi*Math.rint(0.5d*rotVal/pi);          //force between pi and -pi  
        eastBox.setText(Long.toString(Math.round(eastVal/pi180)));   // between 180 and -180 
        northBox.setText(Long.toString(Math.round(northVal/pi180))); // between 180 and -180
        rotBox.setText(Long.toString(Math.round(rotVal/pi180)));     // between 180 and -180
        g.drawImage(sImg, 0, spaceControl, null);
        g.setColor(Color.white);
        g.fillRect(0, 0, size().width, spaceControl);
        g.fillRect(0, endy +spaceControl, size().width, size().height - endy -spaceControl);
        g.fillRect(endx, spaceControl, size().width - endx, endy);
        g.setColor(Color.black);
        g.drawString("Projection",310,40);
        g.drawString("Centre",450,40);
        g.drawString("(East     North    Direction)",520,40);
        } // end of paint method 
    
    public void update (Graphics g) 
        {
        paint(g);
        }
    
        
    public boolean action (Event evt, Object arg) // handle choices from boxes and buttons 
        { //(note depricated)
        if (evt.target == projChoice)  //choose a projection
            {
            proJection = (String) arg;
            if (proJection=="Cylindrical Equal Area") 
                {
                endx = imgWidth;
                endy = (int) (endx / pi);  //Start cylinder scaled correctly at equator
                }
            else if (proJection=="Baar equal area") 
                {
                endy = imgHeight;
                endx = endy;  // Basic border is square around circle
                }
            else if (proJection=="Azimuthal Distance" || proJection=="Azimuthal Equal Area" 
                  || proJection=="Heart Equal Area") 
                {
                endy = imgHeight;
                endx = endy;  // Basic border is square around circle (or Werner Heart)
                }
            else if (proJection=="Conic Distance (fat)" || proJection=="Conic Distance (thin)" 
                  || proJection=="Bonne") 
                {
                endy = imgHeight;
                endx = (int) 4*endy/3; // to give an idea of cut and flattened cone 
                }
            else
                {
                endx = imgWidth;
                endy = (int) endx/2; // 2x1 rectangle 
                }
            }
            
        else if (evt.target == centreChoice)   //pre-selected or random centre and rotation
            {
            projCentre = (String) arg;
            if (projCentre =="Standard")
                {
                eastVal  = 0.0d;
                northVal = 0.0d;
                rotVal   = 0.0d;
                }
            else if (projCentre == "Pacific") 
                {
                eastVal  = pi;
                northVal = 0.0d;
                rotVal   = pi;
                }
            else if (projCentre=="North pole") 
                {
                eastVal  = 0.0d;
                northVal = pi/2.0d;
                rotVal   = 0.0d;
                }
            else if (projCentre=="South pole") 
                {
                eastVal  = 0.0d;
                northVal = -pi/2.0d;
                rotVal   = 0.0d;
                }
            else if (projCentre == "Random")   // random centre and rotation
                {
                eastVal  = pi*(Math.random()*2.0d-1.0d);
                northVal = Math.asin(Math.random()*2.0d-1.0d);
                rotVal   = pi*(Math.random()*2.0d-1.0d);
                }
            else     
                {
                return false;  // event not handled
                }
            } 
        
        else if (evt.target == uChoose)  // choose a particular centre in degrees 
            {                            // then covert to radians
            eastVal  = pi180*Double.valueOf(eastBox.getText().trim()).doubleValue();
            northVal = pi180*Double.valueOf(northBox.getText().trim()).doubleValue();
            rotVal   = pi180*Double.valueOf(rotBox.getText().trim()).doubleValue();
            }
        
        else 
            {
            return false;  // event not handled
            }
        
        repaint();
        return true;   //event handled 
        } // end of action method    
        
        
    public boolean mouseDown (Event evt, int x, int y) 
        {
        endx = x;
        endy = y-spaceControl;
        if (proJection=="Azimuthal Orthographic" || proJection=="Sinusoidal") 
            {
            if (2*endy>endx) // ensure border no narrower than 2x1 rectangle...
                {
                endx = 2*endy;
                }
            if (2*endy<endx) // ...and no wider than 2x1 rectangle
                {
                endy = (int) endx/2;
                }
            }
        else if (proJection=="Azimuthal Distance" || proJection=="Azimuthal Equal Area" ) 
            {
            if (endy>endx) // ensure border no narrower than a square...
                {
                endx = endy;
                }
            if (endy<endx) // ...and no wider than a square
                {
                endy = endx;
                }
            }
        else if (proJection=="Conic Distance (fat)" || proJection=="Heart Equal Area" 
              || proJection=="Bonne") 
            {
            if (endy>endx) // ensure border no narrower than a square...
                {
                endx = endy;
                }
            if (2*endy<endx) // ...and no wider than 2x1 rectangle
                {
                endy = (int) endx/2;
                }
            }
        else if (proJection=="Conic Distance (thin)" && 2*endy<endx) 
            {
            endy = (int) endx/2; // ensure border no no wider than 2x1 rectangle
            }
        repaint();
        return true;
        }    
        
    public boolean mouseDrag (Event evt, int x, int y) 
        {
        return mouseDown(evt, x, y);
        }
        
        
    public int[] newmap (int[] src,                             // image to project
                         int sWid, int sHyt,                    // image   width and height
                         int dWid, int dHyt,                    // display width and height
                         double eAng, double nAng, double rAng, // centre of projection
                         String proJ)                           // name of projection
        {
        // Return a scaled and newmapped version of the image data 
        // Note that Azimuthal type projections involve a transverse map
        int[] buf = new int[dWid * dHyt];       //buf is array with new scaled size
        double scaleX = (double) sWid / dWid;   //scaleX is old width divided by new width
        double scaleY = (double) sHyt / dHyt;   //scaleY is old height divided by new height
        double rectShape = (double) dWid / dHyt;
        
        //create a rotation matrix corrsponding to the three angles (do r then e then n)
        double xcos= Math.cos(nAng);
        double xsin= Math.sin(nAng);
        double ycos= Math.cos(eAng);
        double ysin= Math.sin(eAng);
        double zcos= Math.cos(rAng);
        double zsin= Math.sin(rAng);
        double xxN =  zcos*ycos + zsin*xsin*ysin;
        double xyN = -zsin*ycos + zcos*xsin*ysin;
        double xzN =  xcos*ysin;
        double yxN =  zsin*xcos;
        double yyN =  zcos*xcos;
        double yzN = -xsin;
        double zxN =  zsin*xsin*ycos - zcos*ysin;
        double zyN =  zcos*xsin*ycos + zsin*ysin;
        double zzN =  xcos*ycos;
        // Note some projections (e.g. Azimuthal) adjust this pattern 
        // by reordering variables when they call obliqueRef()
        
        if (proJ=="Longitude Latitude")  // rectangle
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                int sy = (int) (dy * scaleY);                //keep latitude 
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    int sx = (int) (dx * scaleX);            //keep longitude 
                    buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                     xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                    }// end of dx for loop
                }//end of dy for loop
            } // end of LongLat if
        
        else if (proJ=="Mercator")   // conformal rectangle off-edge
            {
            double scaleM=pi/rectShape;
            for (int dy = dHyt; --dy>= 0; )        //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;      // down (up) distance from centre of display
                int sy = (int) (1.0d*sHyt * (2.0d*Math.atan(Math.exp(py*scaleM))/pi) );//mercator
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    int sx = (int) (dx * scaleX);            //keep longitude  
                    buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                     xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Mercator if
        
        else if (proJ=="Cylindrical Equal Area")  // equal area rectangle
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;       // down (up) distance from centre of display
                int sy = (int) (1.0d * sHyt * (Math.asin(py)/pi + 0.5d) ); //for cylindrical
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    int sx = (int) (dx * scaleX);            //keep longitude 
                    buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                     xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Cylindrical if 
        
        else if (proJ=="Sinusoidal")   // equal area
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                 int sy = (int) (dy * scaleY);                //longlat or sinusoidal 
                double py = 2.0d*dy/dHyt-1.0d;        // down (up) distance from centre of display
                double cosy = Math.cos(py*pi/2.0d);            // for sinusiodal projection             
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px = 2.0d*dx/dWid-1.0d;  //right (left) distance from centre of display
                    double pxang = (1.0d + px/cosy);
                    if (pxang<2.0d && pxang>0.0d)             // inside sinusoid
                        {
                        int sx = (int) (0.5d * sWid * pxang);  // sinusoidal old value for row 
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Sinusoidal if 
        
        else if (proJ=="Mollweide")   // equal area ellipse horizontal lat
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;     // down (up) distance from centre of display
                double opy = Math.sqrt(1.0d-py*py);    
                int sy =(int) (1.0d*sHyt*(Math.asin(2.0d*(Math.asin(py)+py*opy)/pi)/pi+0.5d));
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=2.0d*dx/dWid-1.0d;  //right (left) distance from centre of display
                    double pxang = (1.0d+px/opy);
                    if (pxang<2.0d && pxang>0.0d)    // inside limiting ellipse
                        {
                        int sx = (int) (0.5d*sWid*pxang);      //
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Mollweide if
    
        else if (proJ=="Baar equal area")   // varies between Cylindrical equal area and Sinusoidal
            {
            double q=Math.exp(1.0d/(scaleX*scaleX*scaleX*scaleX)); // make q change fast with width
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;   // down (up) distance from centre of display
                int sy = (int) (0.5d * sHyt * (Math.asin(py/q)/Math.asin(1/q) + 1.0d) ); //cyl.
                double cosy = Math.cos(py*pi/2.0d)/Math.cos(py*pi/(2.0d*q)); // for sinusiodal             
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )         //cycle through each new row
                    {
                    double px = 2.0d*dx/dWid-1.0d;   //right (left) distance from centre of display
                    double pxang = (1.0d + px/cosy);
                    if (pxang<2.0d && pxang>0.0d)             // inside sinusoid
                        {
                        int sx = (int) (0.5d * sWid * pxang);  //sinusoidal old value for row 
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Baar if
        
        else if (proJ=="Azimuthal Distance")  //azimuthal circle
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;          // down (up) distance from centre of display
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=2.0d*dx/dWid-1.0d; //right (left) distance from centre of display
                    double pz=Math.sqrt(px*px+py*py);           // Pythagorean distance from centre
                    if (pz < 1.0d)                             // inside limiting circle
                        {
                        int sx = (int) (0.5d*sWid*(Math.atan2(px,py)/pi + 1.0d) ); //polar angle 
                        int sy = (int) (1.0d*sHyt*pz);        //polar distance
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                    xxN, -xzN, xyN, yxN, -yzN, yyN, zxN, -zzN, zyN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Azimuthal Distance if
        
        else if (proJ=="Azimuthal Equal Area")   //azimuthal circle equal area
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;  // down (up) distance from centre of display
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=2.0d*dx/dWid-1.0d;  //right (left) distance from centre of display
                    double pz2=px*px+py*py;      // Pythagorean distance from centre
                    if (pz2 < 1.0d)                               // inside limiting circle
                        {
                        int sx = (int) (0.5d*sWid*(Math.atan2(px,py)/pi + 1.0d) ); //polar angle 
                        int sy = (int) (1.0d*sHyt*Math.acos(1.0d-2.0d*pz2)/pi);//polar equal area 
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                   xxN, -xzN, xyN, yxN, -yzN, yyN, zxN, -zzN, zyN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Azimuthal Equal Area if
        
        else if (proJ=="Azimuthal Orthographic") // azimuthal circle hemisphere (twice) 
            {                                    // parallel views from great distance 
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;  // down (up) distance from centre of display
                int dRow = dy * dWid;
                int dRow1 = (dy+1) * dWid - 1;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=4.0d*dx/dWid-1.0d;  //right (left) distance from centre of display
                    double pz=Math.sqrt(px*px+py*py);      // Pythagorean distance from centre
                    if (pz < 1.0d)                         // inside limiting circle
                        {
                        int sx = (int) (0.5d*sWid*(Math.atan2(px,py)/pi + 1.0d) ); //polar angle 
                        int sy = (int) (1.0d*sHyt*Math.asin(pz)/pi);//polar parallel view
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                   xxN, -xzN, xyN, yxN, -yzN, yyN, zxN, -zzN, zyN)];
                        buf[dRow1 - dx] = src[obliqueRef (sx, sHyt-sy-1, sWid, sHyt, 
                                                   xxN, -xzN, xyN, yxN, -yzN, yyN, zxN, -zzN, zyN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                }// end of dx for loop
            }//end of dy for loop
        } // end of PolarOrthograhic if 
        
        else if (proJ=="Azimuthal Stereographic")   // azimuthal off-edge projection 
            {                                       // from opposite pole to plane touching pole 
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;         // down (up) distance from centre of display
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )       //cycle through each new row
                    {                                  // keep circles 
                    double px=(2.0d*dx-dWid)/dHyt; //right (left) distance from centre of display 
                    double pz=Math.sqrt(px*px+py*py);  // Pythagorean distance from centre
                    int sx = (int) (0.5d*sWid*(Math.atan2(px,py)/pi + 1.0d) );      //polar angle 
                    int sy = (int) (2.0d*sHyt*Math.atan(scaleX*pz)/pi); // Stereographic scaling
                    buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                xxN, -xzN, xyN, yxN, -yzN, yyN, zxN, -zzN, zyN)];
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Azimuthal Stereographic if 
        
        else if (proJ=="Azimuthal Gnomonic")   // azimuthal hemisphere off-edge projection of 
            {                                  // hemisphere from centre to plane touching pole 
            for (int dy = dHyt; --dy>= 0; )             //cycle through each new column
                {
                double py = 2.0d*dy/dHyt-1.0d;  // down (up) distance from centre of display
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )         //cycle through each new row
                    {                                  // keep circles 
                    double px=(2.0d*dx-dWid)/dHyt; //right (left) distance from centre of display 
                    double pz=Math.sqrt(px*px+py*py);       // Pythagorean distance from centre
                    int sx = (int) (0.5d*sWid*(Math.atan2(px,py)/pi + 1.0d) );      //polar angle 
                    int sy = (int) (1.0d*sHyt*Math.atan(scaleX*pz)/pi);//polar projection 2 
                    buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                              xxN, -xzN, xyN, yxN, -yzN, yyN, zxN, -zzN, zyN)];
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Azimuthal Gnomonic if
        
        else if (proJ=="Triangle Equal Area")   // simple version of little practical use 
            {
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 1.0d*dy/dHyt;              // down distance from top of display
                int sy = (int) (1.0d * sHyt * Math.acos(1.0d-2.0d*py*py) / pi );
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=2.0d*dx/dWid-1.0d;     //right (left) distance from centre of display
                    double pxang = 1.0d+px/py;
                    if (pxang<2.0d && pxang>0.0d)         // inside triangle
                        {
                        int sx = (int) (0.5d*sWid*pxang);      //
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Triangle if
        
        else if (proJ=="Conic Distance (fat)")  //based on azimuthal circle            
            {                                   // and equidistance from point of cut
            double cutang = pi-Math.acos(2.0d/rectShape-1.0d);
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 2.0d*(dy-dHyt)/dWid + 1.0d; // down (up) distance from centre of display
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=2.0d*dx/dWid-1.0d;   //right (left) distance from centre of display
                    double pz=Math.sqrt(px*px+py*py);     // Pythagorean distance from centre
                    double pzang=1.0d+Math.atan2(px,py)/cutang;
                    if (pz<1.0d && pzang<2.0d && pzang>0.0d)      // inside limits
                        {
                        int sx = (int) (0.5d*sWid*pzang ); //polar angle 
                        int sy = (int) (1.0d*sHyt*pz);        //polar distance
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Conic Distance if
        
        else if (proJ=="Conic Distance (thin)")  //based on azimuthal circle
            { // and equidistance from point of cut
            double cutang = Math.asin(0.5d*rectShape);
            if (dWid>=2*dHyt) 
                {
                cutang = pi/2.0d;  // just in case
                }
            for (int dy = dHyt; --dy>= 0; )                 //cycle through each new column
                {
                double py = 1.0d*dy/dHyt;    // down (up) distance from centre of display
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )             //cycle through each new row
                    {
                    double px=(1.0d*dx-0.5d*dWid)/dHyt;//right(left)distance from centre of display
                    double pz=Math.sqrt(px*px+py*py);        // Pythagorean distance from centre
                    double pzang=1.0d+Math.atan2(px,py)/cutang;
                    if (pz<1.0d && pzang<2.0d && pzang>0.0d)      // inside limits
                        {
                        int sx = (int) (0.5d*sWid*pzang ); //polar angle 
                        int sy = (int) (1.0d*sHyt*pz);        //polar distance
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Conic Distance if
                
        else if (proJ=="Heart Equal Area")  //unbending a Werner into a sinusoidal projection 
            {      // equal area but not Bonne - latitude lines are concentric similar ellipses 
                   // with centre at pole
            double disTort=(2.0d-rectShape);
            if (disTort<=0.0d) //avoid later division by zero
                {
                disTort=0.0000001d;
                }
            double pyScale=0.65d;    //do not waste too much space when heart shaped
            double pxScale=pyScale*disTort;             //stretch height/width ratio
            for (int dy = dHyt; --dy>= 0; )             //cycle through each new column
                {
                double py = pyScale*(2.0d*dy/dHyt-2.0d)+1.0d; //down (up) distance from centre 
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )         //cycle through each new row
                    {                                  
                    double px=pxScale*(2.0d*dx-dWid)/dHyt; //right (left) distance from centre 
                    double pz=Math.sqrt(px*px+py*py);        // Pythagorean distance from centre
                    double pzang=1.0d + Math.atan2(px,py)*pz/(Math.sin(pi*pz)*disTort);
                    if (pz < 1.0d && pzang < 2.0d && pzang > 0.0d)     // inside limits
                        {
                        int sx = (int) (0.5d*sWid*pzang); //polar angle 
                        int sy = (int) (1.0d*sHyt*pz);        //polar distance
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Heart if
        
        else if (proJ=="Bonne")  //unbending Werner into a sinusoidal projection - Bonne equal area 
            {                    // latitude lines are concentric circles 
                                 // with centre on extension of prime meridian
            double distAbove=10000000.0d; //avoid division by zero 
            if (rectShape<2.0d) 
                {
                distAbove=1.0d/(2.0d-rectShape)-1.0d;
                }
            double pyScale=0.65d;    //do not waste too much space when heart shaped
            double pxScale=pyScale;  //keep height/width ratio
            for (int dy = dHyt; --dy>= 0; )             //cycle through each new column
                {
                double py = pyScale*(2.0d*dy/dHyt-2.0d)+1.0d;   // down (up) distance
                double pydA=py+distAbove;                // adjust for distant centre               
                int dRow = dy * dWid;
                for (int dx = dWid; --dx >= 0; )         //cycle through each new row
                    {                                  // keep circles 
                    double px=pxScale*(2.0d*dx-dWid)/dHyt;  //right (left) distance
                    double pz=Math.sqrt(px*px+pydA*pydA)-distAbove;//centre dist
                    double pzang=1.0d + Math.atan2(px,pydA)*(pz+distAbove)/(Math.sin(pi*pz));
                    if (pz < 1.0d && pz > 0.0d && pzang < 2.0d && pzang > 0.0d)     // inside limits
                        {
                        int sx = (int) (0.5d*sWid*pzang); //polar angle 
                        int sy = (int) (1.0d*sHyt*pz);    //polar distance
                        buf[dRow + dx] = src[obliqueRef (sx, sy, sWid, sHyt, 
                                                    xxN, xyN, xzN, yxN, yyN, yzN, zxN, zyN, zzN)];
                        }
                    else 
                        {
                        buf[dRow + dx] = -16777215;      // black for off edge
                        }
                    }// end of dx for loop
                }//end of dy for loop
            } // end of Bonne if
        
        return buf;  // send back new array of image
        } // end of newmap method
        
    public int obliqueRef (double xCo, double yCo, 
                           int sWide, int sHyte, 
                           double xX, double xY, double xZ, 
                           double yX, double yY, double yZ, 
                           double zX, double zY, double zZ) 
        {
        //using the rotation matrix to get from pixels in the new image 
        // back to pixels in the original image
        double sHpied = 1.0d*sHyte/pi; 
        double sWpied = 1.0d*sWide/pi; 
        double yang = 1.0d*yCo/sHpied;
        double yp  = Math.cos(yang);
        double ysp = Math.sin(yang);
        double xang = 2.0d*xCo/sWpied;
        double xp  = Math.sin(xang)*ysp;
        double zp  = Math.cos(xang)*ysp;
        double xpN = xp*xX+yp*xY+zp*xZ;
        double ypN = xp*yX+yp*yY+zp*yZ;
        double zpN = xp*zX+yp*zY+zp*zZ;
        double xpT = 1.0d - Math.atan2(xpN,-zpN) / pi ;
        int xpixN = (int) (0.5d * sWide * xpT);  
        int ypixN = (int) (1.0d * sHpied * Math.acos(ypN) );  
        int piX = ypixN * sWide + xpixN;
        if (piX >= sWide*sHyte) 
            {
            return sWide*sHyte-1;
            }
        else if (piX < 0) 
            {
            return 0;
            }
        return piX;     
        } // end of obliqueref method
        
        
    public void destroy()  // do I need this?
        {
        }  
                
    } // End of Projects class

