/** * 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*endyendx) // ensure border no narrower than a square... { endx = endy; } if (endyendx) // ensure border no narrower than a square... { endx = endy; } if (2*endy= 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