package com.bbn.openmap.proj; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.geom.Point2D; import java.util.ArrayList; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.proj.coords.UTMGCT; import com.bbn.openmap.proj.coords.UTMPoint; /** * A OpenMap Projection class that uses the {@link UTMPoint} to do its * calculation. */ public class UTMProjection extends GeoProj { /** * Center of view as xy coordinates relative to the underlying projection */ // TODO: find a better name? protected Point2D.Double xycenter = new Point2D.Double(); protected double hy, wx; /** * Pixel per map unit. this is for a projection with a quadratic grid like * utm */ protected double ppu; // used for calculating wrapping of ArrayList graphics // TODO: copied from Cylindrical. may need to change. protected Point world; // world width in pixels. protected int half_world; // world.x / 2 protected int zoneNumber; protected boolean northern; protected Ellipsoid ellps; public UTMProjection(LatLonPoint center, float s, int w, int h, int zone_number, boolean isnorthern, Ellipsoid ellps) { super(center, s, w, h); this.zoneNumber = zone_number; this.northern = isnorthern; this.ellps = ellps; } protected void computeParameters() { // super.computeParameters(); hy = height / 2; wx = width / 2; if (xycenter != null) { UTMPoint c = UTMPoint.LLtoUTM(getCenter(), ellps, new UTMPoint(), zoneNumber, northern); xycenter.setLocation(c.easting, c.northing); } // width of the world in pixels at current scale // TODO: copied from Cylindrical. may need to change if (world == null) { world = new Point(); } world.x = (int) (planetPixelCircumference / scale); half_world = world.x / 2; ppu = (((float) pixelsPerMeter) / getScale()); } public Point2D forward(LatLonPoint llp, Point2D pt) { return forward(llp, pt, new UTMPoint()); } @Override public Point2D forward(double lat, double lon, Point2D pt, boolean isRadian) { LatLonPoint llp = new LatLonPoint.Double(lat, lon, isRadian); return forward(llp, pt, new UTMPoint()); } public Point2D forward(double lat, double lon, Point2D pt, boolean isRadian, UTMPoint utmPoint) { LatLonPoint llp = new LatLonPoint.Double(lat, lon, isRadian); return forward(llp, pt, utmPoint); } private Point2D forward(LatLonPoint llp, Point2D pt, UTMPoint utmPoint) { utmPoint = UTMPoint.LLtoUTM(llp, ellps, utmPoint, zoneNumber, northern); pt.setLocation((wx + (ppu * (utmPoint.easting - xycenter.getX()))), (hy - (ppu * (utmPoint.northing - xycenter.getY())))); return pt; } public <T extends Point2D> T inverse(double x, double y, T llpt) { double northing = xycenter.getY() + ((hy - y) / ppu); double easting = xycenter.getX() + ((x - wx) / ppu); if (!(llpt instanceof LatLonPoint)) { llpt = (T) new LatLonPoint.Double(); } llpt = (T) UTMPoint.UTMtoLL(ellps, northing, easting, zoneNumber, northern, (LatLonPoint) llpt); return llpt; } /** * Given a couple of points representing a bounding box, find out what the * scale should be in order to make those points appear at the corners of * the projection. * * @param ll1 * the upper left coordinates of the bounding box. * @param ll2 * the lower right coordinates of the bounding box. * @param point1 * a java.awt.Point reflecting a pixel spot on the projection * that matches the ll1 coordinate, the upper left corner of the * area of interest. * @param point2 * a java.awt.Point reflecting a pixel spot on the projection * that matches the ll2 coordinate, usually the lower right * corner of the area of interest. */ @Override public float getScale(Point2D ll1, Point2D ll2, Point2D point1, Point2D point2) { // super does not calculate scale correct for projections that does use // the same earth radius up north.. double widthPX = point2.getX() - point1.getX(); // float heightPX = point2.y - point1.y; // Instead of blindly casting, lets just make sure we have the correct object type. LatLonPoint llp1 = LatLonPoint.getDouble(ll1); LatLonPoint llp2 = LatLonPoint.getDouble(ll2); UTMPoint xx1 = UTMPoint.LLtoUTM(llp1, ellps, new UTMPoint(), zoneNumber, northern); UTMPoint xx2 = UTMPoint.LLtoUTM(llp2, ellps, new UTMPoint(), zoneNumber, northern); double widthMap = (xx2.easting - xx1.easting); float widthScale = (float) (((double) getPPM()) * (widthMap / widthPX)); // float heightMap = (xx2.northing - xx1.northing); // float heightScale = (float) (((double) getPPM()) * (heightMap / // heightPX)); // TODO: use width-, height- or medium scale? I guess width- and height // scale should be equal as the grid inside a single UTM zone is // quadratic. return widthScale; } protected ArrayList<float[]> _forwardPoly(float[] rawllpts, int ltype, int nsegs, boolean isFilled) { // TODO: copied from Cylindrical. may need to change. int n, k, flag = 0, min = 0, max = 0, xp, xadj = 0; // determine length of pairs list int len = rawllpts.length >> 1; // len/2, chop off extra if (len < 2) return new ArrayList<float[]>(0); // handle complicated line in specific routines if (isComplicatedLineType(ltype)) { return doPolyDispatch(rawllpts, ltype, nsegs, isFilled); } // determine when to stop Point temp = new Point(0, 0); float[] xs = new float[len]; float[] ys = new float[len]; // more temp objects to limit number of new objects that needs to be // created UTMPoint tempUtm = new UTMPoint(); LatLonPoint tempLL = new LatLonPoint.Double(); // forward project the first point tempLL.setLatLon(rawllpts[0], rawllpts[1], true); forward(tempLL, temp, tempUtm); // forward(rawllpts[0], rawllpts[1], temp, true, tempUtm); xp = temp.x; xs[0] = temp.x; ys[0] = temp.y; // forward project the other points for (n = 1, k = 2; n < len; n++, k += 2) { tempLL.setLatLon(rawllpts[k], rawllpts[k + 1], true); forward(tempLL, temp, tempUtm); // forward(rawllpts[k], rawllpts[k + 1], temp, true, tempUtm); xs[n] = temp.x; ys[n] = temp.y; // segment crosses longitude along screen edge if (Math.abs(xp - xs[n]) >= half_world) { flag += (xp < xs[n]) ? -1 : 1;// inc/dec the wrap // count min = (flag < min) ? flag : min;// left wrap count max = (flag > max) ? flag : max;// right wrap count xadj = flag * world.x;// adjustment to x coordinates // Debug.output("flag=" + flag + " xadj=" + xadj); } xp = temp.x;// save previous unshifted x coordinate if (flag != 0) { xs[n] += xadj;// adjust x coordinates } } min *= -1;// positive magnitude // now create the return list ArrayList<float[]> ret_val = null; ret_val = new ArrayList<float[]>(2 + 2 * (max + min)); ret_val.add(xs); ret_val.add(ys); float[] altx = null; /* * if (Debug.debugging("proj")) { dumpPoly(rawllpts, xs, ys); } */ // add the extra left-wrap polys for (int i = 1; i <= min; i++) { altx = new float[xs.length]; xadj = i * world.x;// shift opposite for (int j = 0; j < altx.length; j++) { altx[j] = xs[j] + xadj; } ret_val.add(altx); ret_val.add(ys); /* * if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); } */ } // add the extra right-wrap polys for (int i = 1; i <= max; i++) { altx = new float[xs.length]; xadj = -i * world.x;// shift opposite for (int j = 0; j < altx.length; j++) { altx[j] = xs[j] + xadj; } ret_val.add(altx); ret_val.add(ys); /* * if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); } */ } return ret_val; }// _forwardPoly() @Override protected ArrayList<float[]> _forwardPoly(double[] rawllpts, int ltype, int nsegs, boolean isFilled) { // TODO: copied from Cylindrical. may need to change. int n, k, flag = 0, min = 0, max = 0, xp, xadj = 0; // determine length of pairs list int len = rawllpts.length >> 1; // len/2, chop off extra if (len < 2) return new ArrayList<float[]>(0); // handle complicated line in specific routines if (isComplicatedLineType(ltype)) { return doPolyDispatch(rawllpts, ltype, nsegs, isFilled); } // determine when to stop Point temp = new Point(0, 0); float[] xs = new float[len]; float[] ys = new float[len]; // more temp objects to limit number of new objects that needs to be // created UTMPoint tempUtm = new UTMPoint(); LatLonPoint tempLL = new LatLonPoint.Double(); // forward project the first point tempLL.setLatLon(rawllpts[0], rawllpts[1], true); forward(tempLL, temp, tempUtm); // forward(rawllpts[0], rawllpts[1], temp, true, tempUtm); xp = temp.x; xs[0] = temp.x; ys[0] = temp.y; // forward project the other points for (n = 1, k = 2; n < len; n++, k += 2) { tempLL.setLatLon(rawllpts[k], rawllpts[k + 1], true); forward(tempLL, temp, tempUtm); // forward(rawllpts[k], rawllpts[k + 1], temp, true, tempUtm); xs[n] = temp.x; ys[n] = temp.y; // segment crosses longitude along screen edge if (Math.abs(xp - xs[n]) >= half_world) { flag += (xp < xs[n]) ? -1 : 1;// inc/dec the wrap // count min = (flag < min) ? flag : min;// left wrap count max = (flag > max) ? flag : max;// right wrap count xadj = flag * world.x;// adjustment to x coordinates // Debug.output("flag=" + flag + " xadj=" + xadj); } xp = temp.x;// save previous unshifted x coordinate if (flag != 0) { xs[n] += xadj;// adjust x coordinates } } min *= -1;// positive magnitude // now create the return list ArrayList<float[]> ret_val = null; ret_val = new ArrayList<float[]>(2 + 2 * (max + min)); ret_val.add(xs); ret_val.add(ys); float[] altx = null; /* * if (Debug.debugging("proj")) { dumpPoly(rawllpts, xs, ys); } */ // add the extra left-wrap polys for (int i = 1; i <= min; i++) { altx = new float[xs.length]; xadj = i * world.x;// shift opposite for (int j = 0; j < altx.length; j++) { altx[j] = xs[j] + xadj; } ret_val.add(altx); ret_val.add(ys); /* * if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); } */ } // add the extra right-wrap polys for (int i = 1; i <= max; i++) { altx = new float[xs.length]; xadj = -i * world.x;// shift opposite for (int j = 0; j < altx.length; j++) { altx[j] = xs[j] + xadj; } ret_val.add(altx); ret_val.add(ys); /* * if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); } */ } return ret_val; } /** * Draw the background for the projection. * * @param g * Graphics2D * @param paint * java.awt.Paint to use for the background */ public void drawBackground(Graphics2D g, java.awt.Paint paint) { g.setPaint(paint); drawBackground(g); } /** * Assume that the Graphics has been set with the Paint/Color needed, just * render the shape of the background. */ public void drawBackground(Graphics g) { g.fillRect(0, 0, getWidth(), getHeight()); } public boolean forwardRaw(float[] rawllpts, int rawoff, int[] xcoords, int[] ycoords, boolean[] visible, int copyoff, int copylen) { // TODO: copied from Cylindrical. may need a change Point temp = new Point(); UTMPoint tempUtm = new UTMPoint(); LatLonPoint tempLL = new LatLonPoint.Double(); int end = copylen + copyoff; for (int i = copyoff, j = rawoff; i < end; i++, j += 2) { tempLL.setLatLon(rawllpts[j], rawllpts[j + 1], true); forward(tempLL, temp, tempUtm); // forward(rawllpts[j], rawllpts[j + 1], temp, true, tempUtm); xcoords[i] = temp.x; ycoords[i] = temp.y; visible[i] = true; } // everything is visible return true; } public LatLonPoint getLowerRight() { return inverse(width - 1, height - 1, new LatLonPoint.Double()); } public LatLonPoint getUpperLeft() { return inverse(0, 0, new LatLonPoint.Double()); } public int getZoneNumber() { return zoneNumber; } public void setZoneNumber(int zoneNumber) { this.zoneNumber = zoneNumber; computeParameters(); } public boolean isNorthern() { return northern; } public void setNorthern(boolean northern) { this.northern = northern; computeParameters(); } public Ellipsoid getEllps() { return ellps; } public void setEllps(Ellipsoid ellps) { this.ellps = ellps; computeParameters(); } /* * HACK epsilon: skirt the edge of the infinite. If this is too small then * we get too close to +-INFINITY when we forward project. Tweak this if you * start getting Infinity or NaN's for forward(). */ protected static double epsilon = 0.01f; /** * Sets radian latitude to something sane. This is an abstract function * since some projections don't deal well with extreme latitudes. * * @param lat * float latitude in radians * @return float latitude (-PI/2 <= y <= PI/2) */ public double normalizeLatitude(double lat) { if (lat > NORTH_POLE - epsilon) { return NORTH_POLE - epsilon; } else if (lat < SOUTH_POLE + epsilon) { return SOUTH_POLE + epsilon; } return lat; } public boolean isPlotable(double lat, double lon) { // TODO need to figure out how to calculate this return true; } /** * Convenience method to create a GCT for this projection. For projections * that start with lat/lon coordinates, this will return a LatLonGCT. For * projections that have world coordinates in meters, the GCT will provide a * way to get to those meter coordinates. For instance, a UTMProjection will * return a UTMGCT. * * @return UTMGCT for current projection */ public UTMGCT getGCTForProjection() { return new UTMGCT(getZoneNumber(), isNorthern() ? 'N' : 'S'); } }