package nxm.sys.libg;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;

import nxm.sys.inc.Constants;
import nxm.sys.lib.Midas;

/** A class to transform 2D views.
    <pre>
      Transformation matrix:
        x' = mxx*x + mxb    x = (x'-mxb)/mxx
    </pre>
    @author  Jeff Schoen
 */
public class View implements Constants {

  /** the MPlot associated with this View */
  protected MPlot MP;

  /** Construct a new View
      @param MP the MPlot associated with this View
   */
  public View (MPlot MP) {
    this.MP = MP;
  }

  /** Set the Geo Perspective parameters. The default implementation does nothing since the
      default view is independent of the observer. Subclasses may override this as needed.
      @param arg the array of arguments
      @return true
   */
  public boolean setObserver (double[] arg) {
    return true;
  }

  /** Get the Geo Perspective parameters. The default implementation simply sets the array to
      <code>{ 0, 0, 0 }</code> since the default view is independent of the observer.
      Subclasses may override this as needed.
      @param arg the array of arguments to be set
      @return true
   */
  public boolean getObserver (double[] arg) {
    arg[0]=arg[1]=arg[2]=0;
    return true;
  }

  /** Rotate the view. The default implementation does nothing since the default view is
      independent of the observer. Subclasses may override this as needed.
      @param arg the array of arguments
      @return true
   */
  public boolean rotate (double[] arg) {
    return true;
  }

  /** Initialize parameters for this view */
  public void setup () {
  }

  /** Transform from real world coordinates to pixel
      @param rwc an array of real world coordinates. Passing in a null here will do nothing.
      @param pix the MPoint which holds the pixel coordinates
      @return 1
   */
  public int rwc2pix (double[] rwc, MPoint pix) {
    if (rwc==null || rwc.length<2 || pix==null) return 1;   // Added for Bug 1975
    if (MP.xvy) {
      pix.y = (int)(rwc[0]*MP.mxx + MP.mxb);
      pix.x = (int)(rwc[1]*MP.myy + MP.myb);
    } else {
      // adjust x coordinate to the extended range section of a continuous lat lon plot if needed
      // extended range goes from 180 to 540 longitude (since 3.9.0 for NXM-3284)
      double xCord = rwc[0];
      if ((MP.view == MPlot.V_LATLONCONTINUOUS) && MP.hasExtendedRange()
          && (xCord <= MP.orx1) && ((xCord + 360) < MP.orx2)) { // This needs to be orx1 and orx2 because of the reversed x1 x2 values
        xCord += 360;
      }
      pix.x = (int)(xCord*MP.mxx + MP.mxb);
      pix.y = (int)(rwc[1]*MP.myy + MP.myb);
    }
    return 1;
  }

  /** Transform from real world coordinates to pixel
    @param rwc an array of real world coordinates. Passing in a null here will do nothing.
    @param pix the MPoint which holds the pixel coordinates
    @param rangeAdjust Adjust the range if in new bounds of continuous lat lon plot.
    @return 1
  */
  int rwc2pix (double[] rwc, MPoint pix, boolean rangeAdjust) {
    if (rwc==null || rwc.length<2 || pix==null) return 1;   // Added for Bug 1975
    if (MP.xvy) {
      pix.y = (int)(rwc[0]*MP.mxx + MP.mxb);
      pix.x = (int)(rwc[1]*MP.myy + MP.myb);
    } else {
      double xCord = rwc[0];
      if (rangeAdjust && (MP.view == MPlot.V_LATLONCONTINUOUS) && MP.hasExtendedRange()
          && (xCord <= MP.orx1) && ((xCord + 360) < MP.orx2)) { // This needs to be orx1 and orx2 because of the reversed x1 x2 values
        xCord += 360;
      }
      pix.x = (int)(xCord*MP.mxx + MP.mxb);
      pix.y = (int)(rwc[1]*MP.myy + MP.myb);
    }
    return 1;
  }

  /** Transform pixel to real world coordinates
      @param pix the pixel coordinates. Passing in a null here will do nothing.
      @param rwc the array which holds the real world coordinates
      @return 0
   */
  public int pix2rwc (MPoint pix, double[] rwc) {
    if (pix==null || rwc==null || rwc.length<3) return 0;  // Added for Bug 1975
    if (MP.xvy) {
      rwc[0] = (((double)pix.y-MP.mxb)/MP.mxx);
      rwc[1] = (((double)pix.x-MP.myb)/MP.myy);
    } else {
      rwc[0] = (((double)pix.x-MP.mxb)/MP.mxx);
      rwc[1] = (((double)pix.y-MP.myb)/MP.myy);
    }
    rwc[2] = 0;
    return 0;
  }

  /** Convert real world data to pixels, x values are computed on fixed start and delta parameters.
      @param buf    the buffer. Passing in a null here will do nothing.
      @param off    the offset (this should usually be 0)
      @param dim    the dimension (usually 1 for an array of y values)
      @param npts   the number of points
      @param pix    the pixel buffer
      @param xstart the beginning x coordinate
      @param xdelta the change in position for the x coordinate
      @return The number of points to draw. Note that additional information is provided via
              <code>pix.off</code> and <code>pix.n</code> when {@link Pix#CLIP} is set in <code>pix.flags</code>
              (only {@link MPlot} should be using the {@link Pix#CLIP} flag).
   */
  public int rwc2pix (float[] buf, int off, int dim, int npts, Pix pix, double xstart, double xdelta) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A double[] BUFFER FOR INPUT
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int    i;
    int    j     = off;
    int    xoff  = off/dim;
    int[]  pax   = pix.x;
    int[]  pay   = pix.y;
    int    pixSz = pix.setSize(npts);
    int    n     = calcNumPointsToPlot(buf.length, off, dim, pixSz, pax.length, pay.length); // NXM-3625 avoid extra tests in loop
    double mxx   = MP.mxx;
    double myy   = MP.myy;
    double mxb   = MP.mxb;
    double myb   = MP.myb;
    if (dim==1) {
      for (i=0; i<n; i++,j++) {
        double x= xstart + (xoff+i)*xdelta; // Since 4.0.0 NXM-3624 modified x-data calculation to avoid precision loss
        pax[i] = (int)( x*mxx + mxb); // NXM-3624 fuller x calculation to avoid precision loss
        pay[i] = (int)( (buf[j])*myy + myb);
      }
    } else {
      for (i=0; i<n; i++,j+=dim) {
        double x= xstart + (xoff+i)*xdelta; // Since 4.0.0 NXM-3624 modified x-data calculation to avoid precision loss
        pax[i] = (int)( x*mxx + mxb);
        pay[i] = (int)( (buf[j])*myy + myb);
      }
    }
    return n;
  }

  /** Convert real world data to pixels, x values are computed on fixed start and delta parameters.
      @param buf    the buffer. Passing in a null here will do nothing.
      @param off    the offset (this should usually be 0)
      @param dim    the dimension (usually 1 for an array of y values)
      @param npts   the number of points
      @param pix    the pixel buffer
      @param xstart the beginning x coordinate
      @param xdelta the change in position for the x coordinate
      @return The number of points to draw. Note that additional information is provided via
              <code>pix.off</code> and <code>pix.n</code> when {@link Pix#CLIP} is set in <code>pix.flags</code>
              (only {@link MPlot} should be using the {@link Pix#CLIP} flag).
      @since NeXtMidas 3.3.2
   */
  public int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix, double xstart, double xdelta) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int    i;
    int    j     = off;
    int    xoff  = off/dim;
    int[]  pax   = pix.x;
    int[]  pay   = pix.y;
    int    pixSz = pix.setSize(npts);
    int    n     = calcNumPointsToPlot(buf.length, off, dim, pixSz, pax.length, pay.length); // NXM-3625 avoid extra tests in loop
    double mxx   = MP.mxx;
    double myy   = MP.myy;
    double mxb   = MP.mxb;
    double myb   = MP.myb;
    if (dim==1) {
      for (i=0; i<n; i++,j++) {
        double x = xstart + (xoff+i)*xdelta; // Since 4.0.0 NXM-3624 modified x-data calculation to avoid precision loss
        pax[i] = (int)( x*mxx + mxb); // NXM-3624 fuller x calculation to avoid precision loss
        pay[i] = (int)( (buf[j])*myy + myb);
      }
    } else {
      for (i=0; i<n; i++,j+=dim) {
        double x= xstart + (xoff+i)*xdelta; // Since 4.0.0 NXM-3624 modified x-data calculation to avoid precision loss
        pax[i] = (int)( x*mxx + mxb);
        pay[i] = (int)( (buf[j])*myy + myb);
      }
    }
    return n;
  }

  /** Convert real world data to pixels. This is identical to the version that uses a double[]
      buffer, both versions are provided for performance reasons.
      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer
      @return The number of points to draw. Note that additional information is provided via
              <code>pix.off</code> and <code>pix.n</code> when {@link Pix#CLIP} is set in <code>pix.flags</code>
              (only {@link MPlot} should be using the {@link Pix#CLIP} flag).
   */
  public int rwc2pix (float[] buf, int off, int dim, int npts, Pix pix) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A double[] BUFFER FOR INPUT
    if (buf==null  || pix==null) return 0;  // Added for BUG 1975
    int    i;
    int    j   = off;
    int    n   = pix.setSize(npts);
    int[]  pax = pix.x;
    int[]  pay = pix.y;
    double mxx = MP.mxx;
    double myy = MP.myy;
    double mxb = MP.mxb;
    double myb = MP.myb;
    if ((pix.flags&Pix.CLIP)!=0) {
     if ((MP.view == MPlot.V_LATLON) || (MP.view == MPlot.V_LATLONCONTINUOUS)) { // continuous Lat Lon added in 3.9.0
       return rwc2pixClip(buf,off,dim,npts,pix,false); // Special handling for CLIP
     } else {
      int dx,dy,dxmax=MP.ix21/2, dymax=MP.iy21/2;
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
        pax[i] = (int)(buf[j+0]*mxx + mxb);
        pay[i] = (int)(buf[j+1]*myy + myb);
        if (i==0) continue;
        dx=pax[i]-pax[i-1]; dy=pay[i]-pay[i-1];
        if (dx<-dxmax||dx>dxmax||dy<-dymax||dy>dymax) {
          pix.off=i; pix.n=i; return i;
        }
      }
     }
    } else if (MP.view == MPlot.V_LATLONCONTINUOUS && MP.hasExtendedRange()) {
      // Continuous Lat Lon plots that are beyond the normal map bounds may need
      // special clipping of shapes to wrap around new outer bounds
      // since 3.9.0 for NXM-3294
      return rwc2pixClip(buf,off,dim,npts,pix,false);
    } else if (dim==2) {
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++) {
        pax[i] = (int)(buf[j++]*mxx + mxb);
        pay[i] = (int)(buf[j++]*myy + myb);
      }
    } else {
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
        pax[i] = (int)(buf[j+0]*mxx + mxb);
        pay[i] = (int)(buf[j+1]*myy + myb);
      }
    }
    return n;
  }

  /** Convert real world data to pixels. This is identical to the version that uses a float[]
      buffer, both versions are provided for performance reasons.
      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer
      @return The number of points to draw. Note that additional information is provided via
              <code>pix.off</code> and <code>pix.n</code> when {@link Pix#CLIP} is set in <code>pix.flags</code>
              (only {@link MPlot} should be using the {@link Pix#CLIP} flag).
   */
  public int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix) {
    // default to false for the specialized fill Since 3.9.2
    return rwc2pix (buf, off, dim, npts, pix, false);
  }

  /** Convert real world data to pixels. This is identical to the version that uses a float[]
      buffer, both versions are provided for performance reasons.
      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer
      @param isSpecializedFill Set to true when there is a need to do fill on a polygon that can be split (useful in latloncontinuous view)
      @return The number of points to draw. Note that additional information is provided via
              <code>pix.off</code> and <code>pix.n</code> when {@link Pix#CLIP} is set in <code>pix.flags</code>
              (only {@link MPlot} should be using the {@link Pix#CLIP} flag).
      @since 3.9.2
  */
  int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix, boolean isSpecializedFill) {
    //TODO - switch this method to be the default for public int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix)
    //       do the code that the boolean isLayerShape calls by default
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT EXCEPT THE LAYERSHAPE lines
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int    i;
    int    j   = off;
    int    n   = pix.setSize(npts);
    int[]  pax = pix.x;
    int[]  pay = pix.y;
    double mxx = MP.mxx;
    double myy = MP.myy;
    double mxb = MP.mxb;
    double myb = MP.myb;
    pix.save=false; // This should be false, make sure it is before any processing as it could cause issues in the drawing since 3.9.2
    if ((pix.flags&Pix.CLIP)!=0) {
     if (MP.view == MPlot.V_LATLON || MP.view == MPlot.V_LATLONCONTINUOUS)  { // Lat Lon Continuous added in 3.9.0
       if (!isSpecializedFill) return rwc2pixClip(buf,off,dim,npts,pix, false);
       else {
         // since 3.9.2 check if the shape goes across whole map and combine it, then close it if necessary
         int res = rwc2pixClipAdjustForFill(buf,off,dim,npts,pix, false);
         return pixSpanEntireMap(pix, res, buf[1]>0);
       }
     } else {
      int dx,dy,dxmax=MP.ix21/2, dymax=MP.iy21/2;
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
        pax[i] = (int)(buf[j+0]*mxx + mxb);
        pay[i] = (int)(buf[j+1]*myy + myb);
        if (i==0) continue;
        dx=pax[i]-pax[i-1]; dy=pay[i]-pay[i-1];
        if (dx<-dxmax||dx>dxmax||dy<-dymax||dy>dymax) { // OVER HALF THE RANGE
          pix.off=i; pix.n=i; return i;
        }
      }
     }
    } else if (MP.view == MPlot.V_LATLONCONTINUOUS && MP.hasExtendedRange()) {
        // If we are in continuous mode and outside the default map boundaries, we may
        // need to clip shapes and draw both sides
      if (!isSpecializedFill) return rwc2pixClip(buf,off,dim,npts,pix, false);
      else {
        // since 3.9.2 check if the shape goes across whole map and combine it, then close it if necessary
        int res = rwc2pixClipAdjustForFill(buf,off,dim,npts,pix, false);
        return pixSpanEntireMap(pix, res, buf[1]>0);
      }
    } else if (dim==2) {
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++) {
        pax[i] = (int)(buf[j++]*mxx + mxb);
        pay[i] = (int)(buf[j++]*myy + myb);
      }
    } else {
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
        pax[i] = (int)(buf[j+0]*mxx + mxb);
        pay[i] = (int)(buf[j+1]*myy + myb);
      }
    }
    return n;
  }

  /** Special version of rwc2pix used with CLIP and LatLon or Mercator view.
      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer
      @param mer  whether or not it is mercator
      @return     The number of points to draw
   */
  int rwc2pixClip (float[] buf, int off, int dim, int npts, Pix pix, boolean mer) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A double[] BUFFER FOR INPUT
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int i;
    int j      = off;
    int n      = pix.setSize(npts);
    int[]  pax = pix.x, pay=pix.y;
    double mxx = MP.mxx;
    double mxb = MP.mxb;
    double myy = MP.myy;
    double myb = MP.myb;
    double rx = MP.hasExtendedRange() ? ((MP.getOuterXMax() - MP.getOuterXMin())/2) : 0;// needs to be middle of range (not necessarily 0
    double ry = 0;
    double range = MPlot.LONGITUDE_RANGE;
    double max = MPlot.LONGITUDE_DEFAULT_MAX;
    if (MP.hasExtendedRange()) {
      double p = 0.000001; // precision
      if (MP.getOuterXMax() > MPlot.LONGITUDE_DEFAULT_MAX+p) max = MP.getOuterXMax();
      if (MP.getOuterXMin() < MPlot.LONGITUDE_DEFAULT_MIN-p) max = MP.getOuterXMin() + MPlot.LONGITUDE_RANGE;
    }
    double min = max - MPlot.LONGITUDE_RANGE; //NEW
    double rxWrapHigh = max - MPlot.LONGITUDE_RANGE/4.0;
    double rxWrapLow = min + MPlot.LONGITUDE_RANGE/4.0;

    for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
      double rxp = rx; // previous rx
      double ryp = ry; // previous ry
      rx = buf[j+0];
      ry = buf[j+1];
      while (rx> max) rx -= MPlot.LONGITUDE_RANGE;
      while (rx< min) rx += MPlot.LONGITUDE_RANGE;
      while (ry> +90) ry -= 180;
      while (ry< -90) ry += 180;

      pax[i] = (int)(rx*mxx + mxb);
      pay[i] = (mer)? (int)(ViewMercader.lat2mer(ry)*myy + myb)
                    : (int)(ry*myy + myb);
      if (i == 0) continue;
      // Check for wrap at +180/-180 longitude
      if ((rx>rxWrapHigh && rxp<rxWrapLow) || (rxp>rxWrapHigh && rx<rxWrapLow)) {
        splitLineSegment(rx,ry,rxp,ryp,range,pax,pay,pix,i,mer,mxx,mxb,myy,myb,min,max);
        return i+1;
      }
    }
    return n;
  }

  /** Convert real world map coordinates to pixels. This method takes in two pixel buffers, a primary one
      and another that is used in the event that the coords cross the dateline. This is done in the event that the
      lines are making a polygon that may need to be filled. This differs from {@link #rwc2pixClip(double[], int, int, int, Pix, boolean)}
      in that the MPlot is passed in and this method does not return when crossing the date line.
      The effect of that is that for a crossed date line, the buffer continues and any line being drawn will
      draw along the edge of the map border instead of breaking.<br>
      pixMain The pixel buffer for the main set of pixels<br>
      pixAlt  The pixel buffer for the set of pixels used when the coords have crossed the date line
      @param buf     The real world coords
      @param off     The initial offset into the buffer
      @param dim     2 for x,y or 3 for x,y,x
      @param npts    How many points
      @param MP      The MPlot
      @param pix     The pixel buffer
      @since NeXtMidas 3.1.3
   */
  int rwc2pixClip (double[] buf, int off, int dim, int npts, MPlot MP, Pix pix) {
    if (buf==null || pix==null) return 0;
    double rx=0, rxp;       // current and previous x pos
    double ry=0, ryp;       // current  y pos
    int x, y;               // the pixel coords
    double  mxx = MP.mxx;
    double  mxb = MP.mxb;
    double  myy = MP.myy;
    double  myb = MP.myb;
    boolean mer = !(MP.view==MPlot.V_LATLON || MP.view == MPlot.V_LATLONCONTINUOUS);  // checked this way to mimic existing methods

    if (mer) {
      myy = (double)(MP.iy1-MP.iy2) / (ViewMercader.lat2mer(MP.ry2) - ViewMercader.lat2mer(MP.ry1));
      myb = MP.iy2 - ViewMercader.lat2mer(MP.ry1)*myy;
    }

    int i1 = 0;  // index into main buffer
    int i2 = 0;  // index into alt buffer

    boolean drawMain = true;  // start filling with a single buffer

    Pix pixMain = new Pix(npts,pix.flags);
    Pix pixAlt  = new Pix(npts,pix.flags);

    for (int i=0; i<npts && off<(buf.length-1); i++,off+=dim) {
      rxp = rx; // previous rx
      ryp = ry; // previous ry
      rx = buf[off+0];
      ry = buf[off+1];
      while (rx>+180) rx -= 360;
      while (rx<-180) rx += 360;
      while (ry> +90) ry -= 180;
      while (ry< -90) ry += 180;

      x =                                                    (int)(rx*mxx + mxb);
      y = (mer)? (int)(ViewMercader.lat2mer(ry)*myy + myb) : (int)(ry*myy + myb);

      if ((rx>+90.0 && rxp<-90.0) || (rx<-90.0 && rxp>+90.0)) {  // crossing the dateline

        // the code below adds two points to the arrays. One on each side of the dateline
        int pt = (rx < rxp) ? 180 : -180;
        int newY = getExtrapolatedYPix (rx, rxp, ry, ryp, MP);
        if (!drawMain) pt = -pt;  // reverse the order if not filling the main array
        checkSize(pixMain,i1+2);  // check size since we are adding two points for every border cross
        checkSize(pixAlt,i2+2);
        pixMain.x[i1]   = (int)(pt*mxx + mxb);
        pixMain.y[i1++] = newY;
        pixAlt.x[i2]    = (int)(-pt*mxx + mxb);
        pixAlt.y[i2++]  = newY;

        drawMain = !drawMain;
        pix.save = true;
      }

      if (drawMain) {
        pixMain.x[i1]   = x;
        pixMain.y[i1++] = y;
      } else {
        pixAlt.x[i2]   = x;
        pixAlt.y[i2++] = y;
      }
    }
    // keep track of how many points we put in each array
    pix.n = i2;
    pix.x = pixMain.x;
    pix.y = pixMain.y;
    pix.sx = pixAlt.x;
    pix.sy = pixAlt.y;
    pix.off = npts;
    return i1;
  }

  /** Get the location of a Y pixel point between two other points, one on each side of the dateline  */
  private int getExtrapolatedYPix (double rx, double rxp, double ry, double ryp, MPlot MP) {
    // this method added in 3.1.3 - Bug 2595
    double myy   = MP.myy;
    double myb   = MP.myb;
    boolean mer  = (MP.view==MPlot.V_MERCATOR);
    if (mer) {
      myy = (double)(MP.iy1-MP.iy2) / (ViewMercader.lat2mer(MP.ry2) - ViewMercader.lat2mer(MP.ry1));
      myb = MP.iy2 - ViewMercader.lat2mer(MP.ry1)*myy;
    }

    int base = (rx < rxp) ? 180 : -180;

    double dx1 = rx - rxp + (base*2);      // old dx
    double dy1 = ry - ryp;            // old dy
    double dx2 = base - rxp;           // new dx
    double ryn = (dx2 / (dx1 / dy1)) + ryp;  // new ry

    int newY = (mer)? (int)(ViewMercader.lat2mer(ryn)*myy + myb) : (int)(ryn*myy + myb);

    return newY;
  }

  /** Function to get the y pixel at the location of the given x value from a line
   *
   *  @param rx the first point x
   *  @param ry the first point y
   *  @param rxp the second point x
   *  @param ryp the second point y
   *  @param rxShift the new x to use to find the y
   *  @param crossLeft which side of the map was crossed.
   *  @return the new y in pixel format
   */
  private int getYAtBorder (double rx, double ry, double rxp, double ryp, double rxShift, boolean crossLeft, double myy, double myb) {
    double x1 = rx;
    double y1 = ry;
    double x2 = crossLeft ? rxp + MPlot.LONGITUDE_RANGE : rxp - MPlot.LONGITUDE_RANGE;
    double y2 = ryp;
    boolean mer  = (MP.view==MPlot.V_MERCATOR);
    double slope = (y2 - y1) / (x2 - x1);
    double yIntercept = y1 - (slope * x1);
    double ryn = (slope * rxShift) + yIntercept;
    return (int) ((mer)? (int)(ViewMercader.lat2mer(ryn)*myy + myb) : (ryn*myy + myb));
  }

  private void checkSize (Pix pix, int size) {
    checkSize(pix, size, 0);
  }

  /** Ensure there is room to add another point. If it is too small, we double in size to avoid future copies */
  private void checkSize (Pix pix, int size, int sizeSave) {
    // this method added in 3.1.3 - Bug 2595
    if (pix.x.length <= size || pix.y.length <= size) {
      int[] tmpx = new int[pix.x.length*2];
      int[] tmpy = new int[pix.y.length*2];
      System.arraycopy(pix.x, 0, tmpx, 0, pix.x.length);
      pix.x = tmpx;
      System.arraycopy(pix.y, 0, tmpy, 0, pix.y.length);
      pix.y = tmpy;
    }

    // added "save" pixels in 3.9.2
    if (pix.sx.length <= sizeSave || pix.sy.length <= sizeSave) {
      int[] tmpx = new int[pix.sx.length*2];
      int[] tmpy = new int[pix.sy.length*2];
      System.arraycopy(pix.sx, 0, tmpx, 0, pix.sx.length);
      pix.sx = tmpx;
      System.arraycopy(pix.sy, 0, tmpy, 0, pix.sy.length);
      pix.sy = tmpy;
    }
  }

  /** Special version of rwc2pix used with CLIP and LatLon or Mercator view.
      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer
      @param mer  whether or not it is mercator
      @return     The number of points to draw
   */
  int rwc2pixClip (double[] buf, int off, int dim, int npts, Pix pix, boolean mer) {
    boolean neverClip = ((pix.flags & Pix.NEVERCLIP) != 0)  && (MP.view == MPlot.V_LATLONCONTINUOUS); // since 3.9.1 to salve issue with trying to clip cities
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int i;
    int j      = off;
    int n      = pix.setSize(npts);
    int[]  pax = pix.x, pay=pix.y;
    double mxx = MP.mxx;
    double mxb = MP.mxb;
    double myy = MP.myy;
    double myb = MP.myb;
    double rx = MP.hasExtendedRange() ? ((MP.getOuterXMax() - MP.getOuterXMin())/2) : 0;// needs to be middle of range (not necessarily 0
    double ry = 0;
    double range = MPlot.LONGITUDE_RANGE;
    double max = MPlot.LONGITUDE_DEFAULT_MAX;
    if (mer) {
      myy = (double)(MP.iy1-MP.iy2) / (ViewMercader.lat2mer(MP.ry2) - ViewMercader.lat2mer(MP.ry1));
      myb = MP.iy2 - ViewMercader.lat2mer(MP.ry1)*myy;
    }
    if (MP.hasExtendedRange()) {
      double p = 0.000001; // precision
      if (MP.getOuterXMax() > MPlot.LONGITUDE_DEFAULT_MAX+p) max = MP.getOuterXMax();
      if (MP.getOuterXMin() < MPlot.LONGITUDE_DEFAULT_MIN-p) max = MP.getOuterXMin() + MPlot.LONGITUDE_RANGE;
    }
    double min = max - MPlot.LONGITUDE_RANGE; //NEW
    double rxWrapHigh = max - MPlot.LONGITUDE_RANGE/4.0;
    double rxWrapLow = min + MPlot.LONGITUDE_RANGE/4.0;

    for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
      double rxp = rx; // previous rx
      double ryp = ry; // previous ry
      rx = buf[j+0];
      ry = buf[j+1];
      while (rx> max) rx -= MPlot.LONGITUDE_RANGE;
      while (rx< min) rx += MPlot.LONGITUDE_RANGE;
      while (ry> +90) ry -= 180;
      while (ry< -90) ry += 180;

      pax[i] = (int)(rx*mxx + mxb);
      pay[i] = (mer)? (int)(ViewMercader.lat2mer(ry)*myy + myb)
                    : (int)(ry*myy + myb);
      if (i == 0) continue;
      // Check for wrap at +180/-180 longitude
      if (((rx>rxWrapHigh && rxp<rxWrapLow) || (rxp>rxWrapHigh && rx<rxWrapLow)) && !neverClip) {
        splitLineSegment(rx,ry,rxp,ryp,range,pax,pay,pix,i,mer,mxx,mxb,myy,myb,min,max);
        return i+1;
      }
    }
    return n;
  }

  /** This method will work with shape files and split them correctly.

      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer
      @return     The number of points to draw
      @since NeXtMidas 3.9.2
      NOTE: may want to use this method for everything instead of just layer shape
   */
  int rwc2pixClipAdjustForFill (double[] buf, int off, int dim, int npts, Pix pix, boolean mer) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int n      = pix.setSize(npts);
    // need to make sure the arrays are large enough for adding points
    if(pix.x.length<npts) pix.x = new int[npts];
    if(pix.y.length<npts) pix.y = new int[npts];
    int y, x;
    double mxx = MP.mxx;
    double mxb = MP.mxb;
    double myy = MP.myy;
    double myb = MP.myb;
    double rx = MP.hasExtendedRange() ? ((MP.getOuterXMax() - MP.getOuterXMin())/2) : 0;// needs to be middle of range (not necessarily 0
    double ry = 0;
    double max = MPlot.LONGITUDE_DEFAULT_MAX;
    if (mer) {
      myy = (double)(MP.iy1-MP.iy2) / (ViewMercader.lat2mer(MP.ry2) - ViewMercader.lat2mer(MP.ry1));
      myb = MP.iy2 - ViewMercader.lat2mer(MP.ry1)*myy;
    }
    if (MP.hasExtendedRange()) {
      double p = 0.000001; // precision
      if (MP.getOuterXMax() > MPlot.LONGITUDE_DEFAULT_MAX+p) max = MP.getOuterXMax();
      if (MP.getOuterXMin() < MPlot.LONGITUDE_DEFAULT_MIN-p) max = MP.getOuterXMin() + MPlot.LONGITUDE_RANGE;
    }
    int i1 = 0;  // index into main buffer
    int i2 = 0;  // index into alt buffer
    boolean drawMain = true;
    double min = max - MPlot.LONGITUDE_RANGE; //NEW
    double rxWrapHigh = max - MPlot.LONGITUDE_RANGE/4.0;
    double rxWrapLow = min + MPlot.LONGITUDE_RANGE/4.0;
    pix.sx = new int[npts];
    pix.sy = new int[npts];

    for (int i=0; i<n && i<npts && off<(buf.length-1); i++,off+=dim) {
      double rxp = rx; // previous rx
      double ryp = ry; // previous ry
      rx = buf[off+0];
      ry = buf[off+1];

      while (rx> max) rx -= MPlot.LONGITUDE_RANGE;
      while (rx< min) rx += MPlot.LONGITUDE_RANGE;
      while (ry> +90) ry -= 180;
      while (ry< -90) ry += 180;

      x = (int)(rx*mxx + mxb);
      y = (mer)? (int)(ViewMercader.lat2mer(ry)*myy + myb) : (int)(ry*myy + myb);

      if(i==0) {
        pix.x[i1]   = x;
        pix.y[i1++] = y;
      }
      else {
        boolean crossLeft = rx>rxWrapHigh && rxp<rxWrapLow;
        boolean crossRight = rxp>rxWrapHigh && rx<rxWrapLow;
        if (crossLeft || crossRight) {
          double rxShift;
          double rxpShift;
          if(crossLeft) {
            rxShift = min;
            rxpShift = max;
          }
          else {
            rxShift = max;
            rxpShift = min;
          }
          // Check the size based on the number of points left (npts - i) + the index into the array -
          // should be able to fit as many points past the index starting into the array.
          checkSize(pix, npts-i+i1, npts-i+i2);
          double diffToCompare = (MP.ix2-MP.ix1)*.8;

          // May need to add logic to check the other direction. Have not seen it but it could be possible.
          if(drawMain && i2>0 && pix.x[i1-1] > diffToCompare && pix.x[i1-1] <= pix.sx[i2-1]) {
            int j;
            for(j = 0;j<i2 && pix.sx[j]>diffToCompare;j++) {
              pix.x[i1] = pix.sx[j];
              pix.y[i1++] = pix.sy[j];
            }
            i2-=j; // This should always end up at 0, if there is ever a case that it is not, we need to shift the array appropriately.
            if (i2!=0) Midas.log("The shape may not draw correctly when split", Midas.WARNING);
          }

          int newY =  getYAtBorder(rx, ry, rxp, ryp, rxpShift, crossLeft, myy, myb);
          pix.x[i1] = (int)((drawMain ? rxShift : rxpShift)*mxx + mxb);
          pix.y[i1] = newY;
          pix.sy[i2]  = newY;
          pix.sx[i2]  = (int)((!drawMain ? rxShift : rxpShift)*mxx + mxb);
          i1++;
          i2++;
          drawMain = !drawMain;
          pix.save = true;
        }

        if (drawMain) {
          pix.x[i1]   = x;
          pix.y[i1++] = y;
        } else {
          pix.sx[i2]   = x;
          pix.sy[i2++] = y;
        }
      }
    }
    pix.n = i2;
    pix.off = npts;
    return i1;
  }

  /** Split into two line segments, to simulate Left boundary /right boundary wrap,
   *  put the first in the pax/pay buffers and put the second in
   *  the pix.sx/pix.sy buffers to be plotted separately, do not
   *  update to i++ since next segment will resume at that point.
   *
   * @param rx    x coordinate
   * @param ry    y coordinate
   * @param rxp   previous x coordinate
   * @param ryp   previous y coordinate
   * @param range longitude range
   * @param pax   pixel for x
   * @param pay   pixel for y
   * @param pix   pixel buffer
   * @param i     index of pixel
   * @param mer   true if mercator view
   * @param mxx   local copy of MP.mxx (delta between x pixels)
   * @param mxb   local copy of MP.mxb (lower boundary for x pixels)
   * @param myy   local copy of MP.myy (delta between y pixels)
   * @param myb   local copy of MP.myb (lower boundary for y pixels)
   * @param min   min outer bound
   * @param max   max outer bound
   * @since NeXtMidas 3.9.0 - split into separate method
   */
  private void splitLineSegment (double rx, double ry, double rxp, double ryp, double range,
      int[] pax, int[] pay, Pix pix, int i, boolean mer,
      double mxx, double mxb, double myy, double myb, double min, double max) {
    double rxnNewPix;  // new rx
    double rxNewPax;  // new rx
    double ryn;  // rew ry
    if (rx < rxp) {
      double dx1 = rx - rxp + range;      // old dx
      double dy1 = ry - ryp;            // old dy
      double dx2 = max - rxp;           // new dx
      rxnNewPix = min;                       // new rx (right)
      rxNewPax = max;
      ryn = (dx2 / (dx1 / dy1)) + ryp;  // new ry
    }
    else {
      double dx1 = rx - rxp - range;      // old dx
      double dy1 = ry - ryp;            // old dy
      double dx2 = min - rxp;          // new dx
      rxnNewPix = max;                        // new rx (left)
      rxNewPax = min;
      ryn = (dx2 / (dx1 / dy1)) + ryp;  // new ry
    }
    pix.sx[0] = (int)(rxnNewPix*mxx + mxb);
    pix.sx[1] = pax[i];
    pix.sy[0] = (mer)? (int)(ViewMercader.lat2mer(ryn)*myy + myb)
    : (int)(ryn*myy + myb);
    pix.sy[1] = pay[i];
    pax[i]    = (int)(rxNewPax*mxx + mxb);
    pay[i]    = pix.sy[0];
    pix.save  = true;
    pix.off   = i;
    pix.n     = 2; // pix.n is the length of the save array and in this instance it is always 2 since 3.9.2
  }

  //************************************************************************************************
  //***************new 3.9.1 high precision double pixel methods ***********************************

  /** Convert RWC coordinates to high precision double pix
   *
   *  @param rwc real world coordinates
   *  @param pix buffer for double pixels
   *  @return 1
   *  @since NeXtMidas 3.9.1
   */
  int rwc2dpix (double[] rwc, DPix pix) {
    if (rwc==null || rwc.length<2 || pix==null) return 1;   // Added for Bug 1975
    if (MP.xvy) {
      pix.y[0] = rwc[0]*MP.mxx + MP.mxb;
      pix.x[0] = rwc[1]*MP.myy + MP.myb;
    } else {
      // adjust x coordinate to the extended range section of a continuous lat lon plot if needed
      // extended range goes from 180 to 540 longitude (since 3.9.0 for NXM-3284)
      double xCord = rwc[0];
      if ((MP.view == MPlot.V_LATLONCONTINUOUS) && MP.hasExtendedRange()
          && (xCord <= MP.orx1) && ((xCord + 360) < MP.orx2)) { // This needs to be orx1 and orx2 because of the reversed x1 x2 values
        xCord += 360;
      }
      pix.x[0] = xCord*MP.mxx + MP.mxb;
      pix.y[0] = rwc[1]*MP.myy + MP.myb;
    }
    pix.n = 1;
    return 1;
  }

  /** Convert real world data to double pixels. This is identical to the version that uses a float[]
    buffer, both versions are provided for performance reasons.
    @param buf  the buffer. Passing in a null here will do nothing.
    @param off  the offset (this should usually be 0)
    @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
    @param npts the number of points
    @param pix  the double pixel buffer
    @return The number of points to draw. Note that additional information is provided via
            <code>pix.off</code> and <code>pix.n</code> when {@link Pix#CLIP} is set in <code>pix.flags</code>
            (only {@link MPlot} should be using the {@link Pix#CLIP} flag).
    @since NeXtMidas 3.9.1
  */
  public int rwc2dpix (double[] buf, int off, int dim, int npts, DPix pix) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int    i;
    int    j   = off;
    int    n   = pix.setSize(npts);
    double[]  pax = pix.getX();
    double[]  pay = pix.getY();
    double mxx = MP.mxx;
    double myy = MP.myy;
    double mxb = MP.mxb;
    double myb = MP.myb;
    if ((pix.getFlags()&Pix.CLIP)!=0) {
     if (MP.view == MPlot.V_LATLON || (MP.view == MPlot.V_LATLONCONTINUOUS)) { // Lat Lon Continuous added in 3.9.0
       return rwc2dpixClip(buf,off,dim,npts,pix,false); // Special handling for CLIP
     } else {
      double dx,dy,dxmax=MP.ix21/2, dymax=MP.iy21/2;
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
        pax[i] = buf[j+0]*mxx + mxb;
        pay[i] = buf[j+1]*myy + myb;
        if (i==0) continue;
        dx=pax[i]-pax[i-1]; dy=pay[i]-pay[i-1];
        if (dx<-dxmax||dx>dxmax||dy<-dymax||dy>dymax) { // OVER HALF THE RANGE
          pix.setOffset(i); pix.setSize(i); return i;
        }
      }
     }
    } else if (MP.view == MPlot.V_LATLONCONTINUOUS && MP.hasExtendedRange()) {
        // If we are in continuous mode and outside the default map boundaries, we may
        // need to clip shapes and draw both sides
        return rwc2dpixClip(buf,off,dim,npts,pix,false);
    } else if (dim==2) {
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++) {
        pax[i] = buf[j++]*mxx + mxb;
        pay[i] = buf[j++]*myy + myb;
      }
    } else {
      for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
        pax[i] = buf[j+0]*mxx + mxb;
        pay[i] = buf[j+1]*myy + myb;
      }
    }
    return n;
  }

  /** Convert real world map coordinates to double pixels. This method takes in two pixel buffers, a primary one
    and another that is used in the event that the coords cross the dateline. This is done in the event that the
    lines are making a polygon that may need to be filled. This differs from {@link #rwc2pixClip(double[], int, int, int, Pix, boolean)}
    in that the MPlot is passed in and this method does not return when crossing the date line.
    The effect of that is that for a crossed date line, the buffer continues and any line being drawn will
    draw along the edge of the map border instead of breaking.<br>
    pixMain The double pixel buffer for the main set of pixels<br>
    pixAlt  The double pixel buffer for the set of pixels used when the coords have crossed the date line
    @param buf     The real world coords
    @param off     The initial offset into the buffer
    @param dim     2 for x,y or 3 for x,y,x
    @param npts    How many points
    @param MP      The MPlot
    @param pix     The double pixel buffer
    @since NeXtMidas 3.9.1
  */
  int rwc2dpixClip (double[] buf, int off, int dim, int npts, MPlot MP, DPix pix) {
    if (buf==null || pix==null) return 0;
    double rx=0, rxp;       // current and previous x pos
    double ry=0, ryp;       // current  y pos
    double x, y;               // the pixel coords
    double  mxx = MP.mxx;
    double  mxb = MP.mxb;
    double  myy = MP.myy;
    double  myb = MP.myb;
    boolean mer = !(MP.view==MPlot.V_LATLON || MP.view == MPlot.V_LATLONCONTINUOUS);  // checked this way to mimic existing methods

    if (mer) {
      myy = (double)(MP.iy1-MP.iy2) / (ViewMercader.lat2mer(MP.ry2) - ViewMercader.lat2mer(MP.ry1));
      myb = MP.iy2 - ViewMercader.lat2mer(MP.ry1)*myy;
    }

    int i1 = 0;  // index into main buffer
    int i2 = 0;  // index into alt buffer

    boolean drawMain = true;  // start filling with a single buffer

    DPix pixMain = new DPix(npts,pix.getFlags());
    DPix pixAlt  = new DPix(npts,pix.getFlags());

    for (int i=0; i<npts && off<(buf.length-1); i++,off+=dim) {
      rxp = rx; // previous rx
      ryp = ry; // previous ry
      rx = buf[off+0];
      ry = buf[off+1];
      while (rx>+180) rx -= 360;
      while (rx<-180) rx += 360;
      while (ry> +90) ry -= 180;
      while (ry< -90) ry += 180;

      x = (rx*mxx + mxb);
      y = (mer)? (ViewMercader.lat2mer(ry)*myy + myb) : (ry*myy + myb);

      if ((rx>+90.0 && rxp<-90.0) || (rx<-90.0 && rxp>+90.0)) {  // crossing the dateline

        // the code below adds two points to the arrays. One on each side of the dateline
        int pt = (rx < rxp) ? 180 : -180;
        int newY = getExtrapolatedYPix (rx, rxp, ry, ryp, MP);
        if (!drawMain) pt = -pt;  // reverse the order if not filling the main array
        checkSize(pixMain,i1+2);  // check size since we are adding two points for every border cross
        checkSize(pixAlt,i2+2);
        pixMain.x[i1]   = pt*mxx + mxb;
        pixMain.y[i1++] = newY;
        pixAlt.x[i2]    = -pt*mxx + mxb;
        pixAlt.y[i2++]  = newY;

        drawMain = !drawMain;
        pix.save = true;
      }

      if (drawMain) {
        checkSize(pixMain,i1+2); // Added just in case - it will only check if we need more points and double the size of the array if it does.
        pixMain.x[i1]   = x;
        pixMain.y[i1++] = y;
      } else {
        checkSize(pixAlt,i2+2); // Added just in case - it will only check if we need more points and double the size of the array if it does.
        pixAlt.x[i2]   = x;
        pixAlt.y[i2++] = y;
      }
    }
    // keep track of how many points we put in each array
    pix.n = i2;
    pix.x = pixMain.x;
    pix.y = pixMain.y;
    pix.sx = pixAlt.x;
    pix.sy = pixAlt.y;
    pix.off = npts;
    return i1;
  }

  /** Special double version of rwc2pix used with CLIP and LatLon or Mercator view.
      @param buf  the buffer. Passing in a null here will do nothing.
      @param off  the offset (this should usually be 0)
      @param dim  the dimension, use 2 or 3 to indicate type of array (2=(x,y), 3=(x,y,z))
      @param npts the number of points
      @param pix  the pixel buffer (doubles)
      @param mer  whether or not it is mercator
      @return     The number of points to draw
      @since NeXtMidas 3.9.1 for NXM-3131
  */
  int rwc2dpixClip (double[] buf, int off, int dim, int npts, DPix pix, boolean mer) {
    boolean neverClip = ((pix.flags & Pix.NEVERCLIP) != 0);
    if (buf==null || pix==null) return 0;  // Added for BUG 1975
    int i;
    int j      = off;
    int n      = pix.setSize(npts);
    double[]  pax = pix.getX(), pay=pix.getY();
    double mxx = MP.mxx;
    double mxb = MP.mxb;
    double myy = MP.myy;
    double myb = MP.myb;
    double rx = MP.hasExtendedRange() ? ((MP.getOuterXMax() - MP.getOuterXMin())/2) : 0;// needs to be middle of range (not necessarily 0
    double ry = 0;
    double range = MPlot.LONGITUDE_RANGE;
    double max = MPlot.LONGITUDE_DEFAULT_MAX;
    if (MP.hasExtendedRange()) {
      double p = 0.000001; // precision
      if (MP.getOuterXMax() > MPlot.LONGITUDE_DEFAULT_MAX+p) max = MP.getOuterXMax();
      if (MP.getOuterXMin() < MPlot.LONGITUDE_DEFAULT_MIN-p) max = MP.getOuterXMin() + MPlot.LONGITUDE_RANGE;
    }
    double min = max - MPlot.LONGITUDE_RANGE; //NEW
    double rxWrapHigh = max - MPlot.LONGITUDE_RANGE/4.0;
    double rxWrapLow = min + MPlot.LONGITUDE_RANGE/4.0;

    for (i=0; i<n && i<pax.length && i<pay.length && j<(buf.length-1); i++,j+=dim) {
      double rxp = rx; // previous rx
      double ryp = ry; // previous ry
      rx = buf[j+0];
      ry = buf[j+1];
      while (rx> max) rx -= MPlot.LONGITUDE_RANGE;
      while (rx< min) rx += MPlot.LONGITUDE_RANGE;
      while (ry> +90) ry -= 180;
      while (ry< -90) ry += 180;

      pax[i] = rx*mxx + mxb;
      pay[i] = (mer)? (ViewMercader.lat2mer(ry)*myy + myb)
                    : (ry*myy + myb);
      if (i == 0) continue;
      // Check for wrap at +180/-180 longitude
      if (((rx>rxWrapHigh && rxp<rxWrapLow) || (rxp>rxWrapHigh && rx<rxWrapLow)) && !neverClip) {
        splitLineSegment(rx,ry,rxp,ryp,range,pax,pay,pix,i,mer,mxx,mxb,myy,myb,min,max);
        return i+1;
      }
    }
    return n;
  }

  /** Calculate the number of points we can plot (i.e. number of loops)
   *  based both on real world data buffer and Pix buffers limitations
   *
   *  @param bufLength length of the real world data buffer
   *  @param bufOff    the offset (this should usually be 0)
   *  @param dim       the dimension (usually 1 for an array of y values)
   *  @param pixSize   Pix feedback on number of points to do this pass
   *  @param paxLength length of the pix.x array
   *  @param payLength length of the pix.y array
   *  @return number of points to do (loops to execute) based on both pix & buff
   *  @since NeXtMidas 3.9.4 for NXM-3625
   */
  int calcNumPointsToPlot (int bufLength, int bufOff, int dim, int pixSize, int paxLength, int payLength) {
    // the bufN check replaces the earlier j < buf.length check, but improves it by
    // making sure at least 2 buf elements are available if dim > 1
    int numElemBuf = bufLength-bufOff; // # elements available in the buffer
    int numPerPass = Math.min(dim, 2); // # actually needed each pass (max of 2 values actually used)

    // Calculate number of passes/loops based on # element available in buffer and dim
    // Make sure enough for 1 pass, and then add on number of additional passes possible
    int    bufN = (numElemBuf >= numPerPass) ? 1 + ((numElemBuf - numPerPass)/dim) : 0;

    // Calculate number of passes based on Pix sizes
    int    pixN   = Math.min(pixSize,Math.min(paxLength, payLength));

    // Actual number of loops to execute is minimum of the two
    return Math.min(bufN, pixN);
  }

  /** Split into two line segments, to simulate Left boundary /right boundary wrap,
   *  put the first in the pax/pay buffers and put the second in
   *  the pix.sx/pix.sy buffers to be plotted separately, do not
   *  update to i++ since next segment will resume at that point.
   *
   * @param rx    x coordinate
   * @param ry    y coordinate
   * @param rxp   previous x coordinate
   * @param ryp   previous y coordinate
   * @param range longitude range
   * @param pax   pixel for x (array of doubles)
   * @param pay   pixel for y (array of doubles)
   * @param pix   pixel buffer
   * @param i     index of pixel
   * @param mer   true if mercator view
   * @param mxx   local copy of MP.mxx (delta between x pixels)
   * @param mxb   local copy of MP.mxb (lower boundary for x pixels)
   * @param myy   local copy of MP.myy (delta between y pixels)
   * @param myb   local copy of MP.myb (lower boundary for y pixels)
   * @param min   min outer bound
   * @param max   max outer bound
   * @since NeXtMidas 3.9.1 - version that accepts high resolution double pixels
   */
  private void splitLineSegment (double rx, double ry, double rxp, double ryp, double range,
      double[] pax, double[] pay, DPix pix, int i, boolean mer,
      double mxx, double mxb, double myy, double myb, double min, double max) {
    double rxnNewPix;  // new rx
    double rxNewPax;  // new rx
    double ryn;  // rew ry
    if (rx < rxp) {
      double dx1 = rx - rxp + range;      // old dx
      double dy1 = ry - ryp;            // old dy
      double dx2 = max - rxp;           // new dx
      rxnNewPix = min;                       // new rx (right)
      rxNewPax = max;
      ryn = (dx2 / (dx1 / dy1)) + ryp;  // new ry
    }
    else {
      double dx1 = rx - rxp - range;      // old dx
      double dy1 = ry - ryp;            // old dy
      double dx2 = min - rxp;          // new dx
      rxnNewPix = max;                        // new rx (left)
      rxNewPax = min;
      ryn = (dx2 / (dx1 / dy1)) + ryp;  // new ry
    }
    pix.sx[0] = rxnNewPix*mxx + mxb;
    pix.sx[1] = pax[i];
    pix.sy[0] = (mer)? (ViewMercader.lat2mer(ryn)*myy + myb)
    : (ryn*myy + myb);
    pix.sy[1] = pay[i];
    pax[i]    = rxNewPax*mxx + mxb;
    pay[i]    = pix.sy[0];
    pix.save  = true;
    pix.off   = i;
    pix.n     = i;
  }

  /** Ensure there is room to add another point. If it is too small, we double in size to avoid future copies */
  private void checkSize (DPix pix, int size) {
    // this method added in 3.1.3 - Bug 2595
    if (pix.getLengthX() <= size || pix.getLengthY() <= size) {
      double[] tmpx = new double[pix.getLengthX()*2];
      double[] tmpy = new double[pix.getLengthY()*2];
      System.arraycopy(pix.getX(), 0, tmpx, 0, pix.getLengthX());
      pix.setX(tmpx);
      System.arraycopy(pix.getY(), 0, tmpy, 0, pix.getLengthY());
      pix.setY(tmpy);
    }
  }

  //***************************************************************************************************


  /** Converts DPix to multiple visible line segment groups<br>
   *  WARNING: Under development - 1st version just written, but not tested
   *  TODO: Handle pix.save with pixels wrapped in Clip - probably just call second time with pix.save pixels
   *  TODO: convert to static method to make for easier testing?????
   *
   *   @param pix  array of high resolution double pixels
   *   @param npts number of points
   *   @param off  offset into pixel buffer
   *   @return array of int Pix visible line segments
   *   @since NextMidas 3.9.1 for NXM-3131
   */
  Pix[] dpix2viewablePix (DPix pix, int npts, int off) {
    Pix[] viewable = null;
    // Note: starting with each pixel ArrayList holding just 1 segment per group,
    // but this is an ArrayList<ArrayList> so multiple connecting segments can be held as 1 group
    ArrayList<ArrayList<Integer>> xPixels = new ArrayList<>();
    ArrayList<ArrayList<Integer>> yPixels = new ArrayList<>();
    // we do not need our visible logic unless there are at least 2 points, because
    // otherwise any pixels are just visible or not
    if (npts < 2) {
      viewable = new Pix[1];
      viewable[0] = pix.toPix();
    }
    // else check based on line segments
    // start with first segment and do the following for each segment
    // 1) if is at least partially contained add visible line to the viewable[segment][pix coordinates]
    // 2) LATER for FILL case will need to check if this need to connect to a previous visible line segment
    //    or have segments added so the fill is right
    // LATER want to speed up processing by at least not needing to re-check pix that are actually in the viewable area
    Point2D.Double visibleBoxMinXY = new Point2D.Double(MP.ix1, MP.iy1);
    Point2D.Double visibleBoxMaxXY = new Point2D.Double(MP.ix2, MP.iy2);
    IntersectingLine visibleSegment = null;
    Line2D.Double originalSegment = null;
    int lineSegmentGrp = 0;
    int maxIndex = Math.min(off + npts, Math.min(pix.getLengthX(), pix.getLengthY())) - 1;
    for (int i = off; i < maxIndex; i++) {
      // Note there is currently redundancy when one of the points is contained in the viewable area.
      // It really should not have to be found twice, but this is simpler
      originalSegment = new Line2D.Double(pix.x[i], pix.y[i], pix.x[i+1], pix.y[i+1]);
      visibleSegment = GeometricUtil.adjustToVisibleLine(originalSegment, visibleBoxMinXY, visibleBoxMaxXY);

      if (visibleSegment !=null) {
        // TODO: In the future when the 2nd point is ENCLOSED we should keep adding to this group until
        // we each a point outside the viewing area.  Then when we are in FILL mode, we should
        // connect the dots to form the visible portion of the shape.
        xPixels.add(new ArrayList<>());
        yPixels.add(new ArrayList<>());
        xPixels.get(lineSegmentGrp).add((int)visibleSegment.line.x1);
        xPixels.get(lineSegmentGrp).add((int)visibleSegment.line.x2);
        yPixels.get(lineSegmentGrp).add((int)visibleSegment.line.y1);
        yPixels.get(lineSegmentGrp).add((int)visibleSegment.line.y2);
        lineSegmentGrp++;
      }
    }
    if (xPixels.size() > 0) {
      viewable = new Pix[xPixels.size()];
      for (int pixGrp = 0; pixGrp < xPixels.size(); pixGrp++) {
        ArrayList<Integer> xPixGrp = xPixels.get(pixGrp);
        ArrayList<Integer> yPixGrp = yPixels.get(pixGrp);
        viewable[pixGrp] = new Pix(pix.max-1,pix.flags);
        viewable[pixGrp].n = xPixGrp.size();
        for (int i = 0; i < viewable[pixGrp].n; i++) {
          viewable[pixGrp].x[i] = xPixGrp.get(i);
          viewable[pixGrp].y[i] = yPixGrp.get(i);
        }
      }
    }

    return viewable;
  }

  /** This will adjust a Pixel object that spans the entire map.
   *
   *  @param pix    The Pixel object holding the points
   *  @param n      The number of points
   *  @param north  True if in the northern hemisphere
   *  @return       The number of points in pix.x
   *  @since NeXtMidas 3.9.2
   */
  int pixSpanEntireMap (Pix pix, int n, boolean north) {
    if(pix.save) {
      // Scenario where the line will go the length of the map (i.e. Antarctica) and needs to be adjusted
      // This will combine the 2 arrays together to make one large shape as the points do connect
      if (pix.x[0] == pix.sx[pix.n-1]) {
        pix.x = combine(pix.sx, pix.n, pix.x, n);
        pix.y = combine(pix.sy, pix.n, pix.y, n);
        n += pix.n;
        pix.save = false;
        // If the polygon does not close on the edges, we need to complete it
        n = completePolygon(pix, n, north);
      }
    }
    return n;
  }

  /** Utility method that will combine 2 arrays
   *
   *  @param a          First array
   *  @param aNumPoints First array number of points
   *  @param b          Second array
   *  @param bNumPoints Second array number of points
   *  @return The new array
   *  @since NeXtMidas 3.9.2
   */
  static int[] combine(int[] a, int aNumPoints, int[] b, int bNumPoints){
    int[] result = new int[aNumPoints+bNumPoints];
    System.arraycopy(a, 0, result, 0, aNumPoints);
    System.arraycopy(b, 0, result, aNumPoints, bNumPoints);
    return result;
  }

  /** This will close a polygon that is does not close on its own.
   *  It will use -90 for the southern hemisphere or +90 for the northern.
   *  @param pix        The Pixel object that contains the points
   *  @param n          The number of points
   *  @param maxPixels  The max number of pixels (points) that there could be.
   *  @param north      True if in northern hemisphere
   *  @return           the number of points
   *  @since NeXtMidas 3.9.2
   */
  int completePolygon(Pix pix, int n, boolean north) {
    if (pix.x[0]==pix.x[n-1]) return n; // the polygon is closed
    int length = n+3;
    // If the length is not good, we need to make a new array of the right length.
    if(pix.x.length<length) {
      int[] resx = new int[length];
      int[] resy = new int[length];
      System.arraycopy(pix.x, 0, resx, 0, n);
      System.arraycopy(pix.y, 0, resy, 0, n);
      pix.x=resx;
      pix.y=resy;
    }
    double yval = north ? 90 : -90;
    // TODO - need to make this work for the northern hemisphere?
    int newY = (int) (yval*MP.myy+MP.myb);
    pix.y[n] = newY;
    pix.y[n+1] = newY;
    pix.y[n+2] = pix.y[0];

    pix.x[n] = pix.x[n-1];
    pix.x[n+1] = pix.x[0];
    pix.x[n+2] = pix.x[0];
    return n+3;

  }

  /** Gets a potential intersection coordinate between the given line and one axis of the
   *  viewing box
   *
   *  @param line 2D line that might intersect or even be fully contained within the specified box
   *  @param axisDistanceLinePt1ToBoxBoundary distance along axis from 1st line point to box line being checked
   *  @param axisDistanceLinePt2ToBoxBoundary distance along axis from 2nd line point to box line being checked
   *  @return potential intersection with box bounding line
   */
  private static Point2D.Double getIntersection (Line2D.Double line, double axisDistanceLinePt1ToBoxBoundary,
      double axisDistanceLinePt2ToBoxBoundary) {
    // if the line is totally to one side of the box line being evaluated => no intersection
    if ((axisDistanceLinePt1ToBoxBoundary * axisDistanceLinePt2ToBoxBoundary) >= 0.0f) return null;
    // if the line is parallel to the box line being evaluated => no intersection
    if (axisDistanceLinePt1ToBoxBoundary == axisDistanceLinePt2ToBoxBoundary) return null;

    // Get the 2D distance line point 1 to line point 2
    double p2MinusP1X = line.x2 - line.x1;
    double p2MinusP1Y = line.y2 - line.y1;

    // Get the ratio (line point 1 to intersect) / (line point 1 to line point 2)
    // Note: This equation is a little confusing because the divisor has parts that cancel out
    double multFactor = -axisDistanceLinePt1ToBoxBoundary /
        (axisDistanceLinePt2ToBoxBoundary - axisDistanceLinePt1ToBoxBoundary);

    // get the 2D distance the potential intersect is from line point 1
    double distIntersectFromP1X = p2MinusP1X * multFactor;
    double distIntersectFromP1Y = p2MinusP1Y * multFactor;

    // get the potential intersect location by adding this 2D distance to line point 1
    double intersectX = line.x1 + distIntersectFromP1X;
    double intersectY = line.y1 + distIntersectFromP1Y;
    return new Point2D.Double(intersectX, intersectY);
  }

}
