[OpenMap Users] Re: ESRIPolygonRecord with holes

From: alexander sokolov <sokolov_at_system.ecology.su.se>
Date: Fri, 25 Jun 2004 16:50:47 +0200

Hi Don,

It seems I found a way to treat ESRIPolygonRecord with holes correctly.
We can use
 OMAreaList instead of OMGraphicList to store OMPolys of the ESRIRecord.
Modifications are very small (see the method addOMGraphics in attached
ESRIPolygonRecord.java)
and also we have to set

firstPoint to true in appendShapeEdge in the BasicGeometry.java.

This modifications works well for me but I am not sure about possible
side effects.

What do you think about this?

Regards

Alexander




OpenMap Support wrote:

> Hi Alexander,
>
> That's correct, holds aren't handled well for either class. The code
> would have to be modified to handle them.
>
> - Don
>
> On Jun 22, 2004, at 8:26 AM, alexander sokolov wrote:
>
>> Hi Don,
>>
>> I'm trying to work a with shape file of seas with islands and
>> it seems neither EsriLayer nor ShapeLayer support ESRI records with
>> holes.
>> I mean it seems openmap does not pay attention to the order of
>> polygon's vertices.
>> (clockwise or counterclockwise). Is it so and what we can do?
>>
>> Regards
>> Alexander
>>
>>
>
>
> =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
> Don Dietrick, dietrick_at_bbn.com
> BBN Technologies, Cambridge, MA
> =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
>


// **********************************************************************
//
// <copyright>
//
// BBN Technologies, a Verizon Company
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/shape/ESRIPolygonRecord.java,v $
// $RCSfile: ESRIPolygonRecord.java,v $
// $Revision: 1.3 $
// $Date: 2004/01/26 18:18:11 $
// $Author: dietrick $
//
// **********************************************************************


package com.bbn.openmap.layer.shape;

import java.io.IOException;
import com.bbn.openmap.omGraphics.*;
import com.bbn.openmap.omGraphics.geom.*;
import com.bbn.openmap.proj.ProjMath;

/**
 * The Polygon record type. This class implements the ESRI Shapefile
 * polygon AND arc/polyline record types.
 * _at_author Ray Tomlinson
 * _at_author Tom Mitchell <tmitchell_at_bbn.com>
 * _at_author HACK-author blame it on aculline
 * _at_version $Revision: 1.3 $ $Date: 2004/01/26 18:18:11 $
 */
public class ESRIPolygonRecord extends ESRIRecord {

    /** Polygon or arc/polyline?. */
    protected int shapeType = SHAPE_TYPE_POLYGON;

    /** The bounding box. */
    public ESRIBoundingBox bounds;

    /** An array of polygons. */
    public ESRIPoly[] polygons;

    public ESRIPolygonRecord() {
        bounds = new ESRIBoundingBox();
        polygons = new ESRIPoly[0];
    }

    /**
     * Initialize a polygon record from the given buffer.
     *
     * _at_param b the buffer
     * _at_param off the offset into the buffer where the data starts
     */
    public ESRIPolygonRecord(byte b[], int off) throws IOException {
        super(b, off);

        int ptr = off+8;

        shapeType = readLEInt(b, ptr);
        ptr += 4;
        if ((shapeType != SHAPE_TYPE_POLYGON) && (shapeType != SHAPE_TYPE_ARC)) {
            throw new IOException("Invalid polygon record. Expected shape " +
                                  "type " + SHAPE_TYPE_POLYGON + " or type " +
                                  SHAPE_TYPE_ARC + ", but found " + shapeType);
        }
        boolean ispolyg = isPolygon();

        bounds = readBox(b, ptr);
        ptr += 32; // A box is 4 doubles (4 x 8bytes)

        int numParts = readLEInt(b, ptr);
        ptr += 4;

        int numPoints = readLEInt(b, ptr);
        ptr += 4;

        if (numParts <= 0) return;

        polygons = new ESRIPoly[numParts];
        int origin = 0;
        int _len;
        for (int i = 0; i < numParts; i++) {

            int nextOrigin = readLEInt(b, ptr);
            ptr += 4;

            if (i > 0) {
                _len = nextOrigin - origin;
                if (ispolyg) ++_len;//connect pairs
                polygons[i-1] = new ESRIPoly.ESRIFloatPoly(_len);
            }
            origin = nextOrigin;
        }
        _len = numPoints - origin;
        if (ispolyg) ++_len;//connect pairs
        polygons[numParts-1] = new ESRIPoly.ESRIFloatPoly(_len);
        for (int i = 0; i < numParts; i++) {
            ptr += polygons[i].read(b, ptr, ispolyg);
        }
    }

    /**
     * Is this a polygon or a arc/polyline?
     * _at_return boolean
     */
    public boolean isPolygon() {
        return shapeType == SHAPE_TYPE_POLYGON;
    }

    /**
     * Set the poly type (polygon or arc/polyline).
     */
    public void setPolygon(boolean isPolygon) {
        shapeType = isPolygon ? SHAPE_TYPE_POLYGON : SHAPE_TYPE_ARC;
    }

    /**
     * Add a poly to the record.
     * @param radians coordinates: y,x,y,x,... (lat,lon) order in
     * RADIANS!
     */
    public void add(float radians[]) {
        ESRIPoly newPoly = new ESRIPoly.ESRIFloatPoly(radians);

        int numParts = polygons.length;
        ESRIPoly oldPolys[] = polygons;
        polygons = new ESRIPoly[numParts + 1];
        for (int i = 0; i < numParts; i++) {
            polygons[i] = oldPolys[i];
        }

        polygons[numParts] = newPoly;

        int len = radians.length;
        for (int i=0; i<len; i+=2) {
            // REMEMBER: switch to x,y order
            bounds.addPoint(
                    ProjMath.radToDeg(radians[i+1]),//x (lon)
                    ProjMath.radToDeg(radians[i]));//y (lat)
        }
    }

    /**
     * Generates 2D OMGraphics and adds them to the given list. If
     * you are using jdk1.1.X, you'll have to comment out this method,
     * because jdk1.1.X doesn't know about the java.awt.Stroke and
     * java.awt.Paint interfaces.
     *
     * _at_param list the graphics list
     * _at_param drawingAttributes the drawingAttributes to paint the poly.
     */
    public void addOMGraphics(OMGraphicList list,
                              DrawingAttributes drawingAttributes) {

        int nPolys = polygons.length;
        if (nPolys <= 0) return;
        OMPoly p=null;
        float[] pts;
        boolean ispolyg = isPolygon();
        /*
         * modifications in the next 4 lines marked with as:
         * allow to treat ESRIPolygonRecord with holes correctly
         * (ESRIPolys with counterclockwise order of vertices)
         *
         * Note: we also have to set firstPoint to true in
         * the BasinsGeometry.appendShapeEdge() (probably a bug)
         * 2004-06-25
         */
                OMAreaList sublist = null; //as: was OMGraphicList sublist = null;

                if (nPolys > 1) {
                        sublist = new OMAreaList(10); //as: was OMGraphicList(10);
                        drawingAttributes.setTo(sublist); //as: new line
        
            sublist.setVague(true); // Treat list as one object.
            list.add(sublist);
            sublist.setAppObject(new Integer(getRecordNumber()));
        }

        for (int i=0, j, k; i<nPolys; i++) {
            // these points are already in RADIAN lat,lon order!...
            pts = ((ESRIPoly.ESRIFloatPoly)polygons[i]).getRadians();
            int len = pts.length;
            p = new OMPoly(pts,
                           OMGraphic.RADIANS,
                           OMGraphic.LINETYPE_STRAIGHT);

            drawingAttributes.setTo(p);
            if (!ispolyg) {
                p.setIsPolygon(false);
            }

            if (sublist != null) {
                sublist.add(p);
            } else {
                // There should be only one.
                p.setAppObject(new Integer(getRecordNumber()));
                list.add(p);
            }
        }
    }

    /**
     * Generates OMGeometry and adds them to the given list.
     *
     * _at_param list the geometry list
     */
    public OMGeometry addOMGeometry(OMGeometryList list) {

        int nPolys = polygons.length;
        if (nPolys <= 0) {
            return null;
        }

        float[] pts;
        boolean ispolyg = isPolygon();
        OMGeometry geom = null;

        for (int i=0, j, k; i<nPolys; i++) {
            // these points are already in RADIAN lat,lon order!...
            pts = ((ESRIPoly.ESRIFloatPoly)polygons[i]).getRadians();
            int len = pts.length;
            if (ispolyg) {
                
                geom = new PolygonGeometry.LL(pts,
                                              OMGraphic.RADIANS,
                                              OMGraphic.LINETYPE_STRAIGHT);
            } else {
                geom = new PolylineGeometry.LL(pts,
                                               OMGraphic.RADIANS,
                                               OMGraphic.LINETYPE_STRAIGHT);
            }
            list.add(geom);
        }
        return geom;
    }

    /**
     * Gets this record's bounding box.
     *
     * _at_return a bounding box
     */
    public ESRIBoundingBox getBoundingBox() {
        return bounds;
    }

    /**
     * Gets this record's shape type as an int. Shape types
     * are enumerated on the ShapeUtils class.
     *
     * _at_return the shape type as an int (either SHAPE_TYPE_POLYGON or
     * SHAPE_TYPE_ARC)
     */
    public int getShapeType() {
        return shapeType;
    }

    /**
     * Yields the length of this record's data portion.
     * <p>
     * (44 + (numParts * 4) + (numPoints * 16))
     * <br>
     * 3 Integers + 4 doubles == 3 * 4bytes + 4 * 8bytes == 12 + 32 == 44.
     *
     * _at_return number of bytes equal to the size of this record's data
     */
    public int getRecordLength() {
        int numParts = polygons.length;
        int numPoints = 0;
        for (int i = 0; i < numParts; i++) {
            numPoints += polygons[i].nPoints;
        }
        return (44 + (numParts * 4) + (numPoints * 16));
    }

    /**
     * Writes this polygon to the given buffer at the given offset.
     *
     * _at_param b the buffer
     * _at_param off the offset
     * _at_return the number of bytes written
     */
    public int write(byte[] b, int off) {
        int nBytes = super.write(b, off);
        nBytes += writeLEInt(b, off + nBytes, shapeType);
        // bounds
        nBytes += writeBox(b, off + nBytes, bounds);
        // numparts
        int numParts = polygons.length;
        nBytes += writeLEInt(b, off + nBytes, numParts);
        // numpoints
        int numPoints = 0;
        for (int i = 0; i < numParts; i++) {
            numPoints += polygons[i].nPoints;
        }
        nBytes += writeLEInt(b, off + nBytes, numPoints);
        // parts
        int ptr = 0;
        for (int i = 0; i < numParts; i++) {
            nBytes += writeLEInt(b, off + nBytes, ptr);
            ptr += polygons[i].nPoints;
        }

        // points
        for (int i = 0; i < numParts; i++) {
            // REMEMBER: stored internally as y,x order (lat,lon order)
            float[] pts = ((ESRIPoly.ESRIFloatPoly)polygons[i]).getRadians();
            int nPts = pts.length;
            for (int j=0; j<nPts; j+=2) {
                nBytes += writeLEDouble(
                        b, off + nBytes, (double)ProjMath.radToDeg(pts[j+1]));//x (lon)
                nBytes += writeLEDouble(
                        b, off + nBytes, (double)ProjMath.radToDeg(pts[j]));//y (lat)
            }
        }

        // return number of bytes written
        return nBytes;
    }
}

// **********************************************************************
//
// <copyright>
//
// BBN Technologies, a Verizon Company
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/omGraphics/geom/BasicGeometry.java,v $
// $RCSfile: BasicGeometry.java,v $
// $Revision: 1.8 $
// $Date: 2004/01/26 18:18:13 $
// $Author: dietrick $
//
// **********************************************************************


package com.bbn.openmap.omGraphics.geom;

import com.bbn.openmap.omGraphics.OMGeometry;
import com.bbn.openmap.omGraphics.OMGraphicConstants;
import com.bbn.openmap.proj.*;
import com.bbn.openmap.util.Debug;

import java.awt.*;
import java.awt.geom.*;
import java.io.Serializable;

/**
 * Base class implementation of OpenMap OMGeometry. <p>
 *
 * The geometry classes are intended to pull the object location data
 * out of the OMGraphics. If you have a bunch of OMGraphics that are
 * all rendered with common attributes, you can create a bunch of
 * OMGeometry objects to plavce in a OMGeometryList that will render
 * them all alike.
 *
 * _at_see PolygonGeometry
 * _at_see PolylineGeometry
 * _at_see com.bbn.openmap.omGraphics.OMGeometryList
 * _at_see Projection
 */
public abstract class BasicGeometry
    implements OMGeometry, Serializable, OMGraphicConstants {

    /**
     * The lineType describes the way a line will be drawn between
     * points. LINETYPE_STRAIGHT will mean the line is drawn straight
     * between the pixels of the endpoints of the line, across the
     * window. LINETYPE_GREATCIRCLE means the line will be drawn on the
     * window representing the shortest line along the
     * land. LINETYPE_RHUMB means a line will be drawn along a constant
     * bearing between the two points.
     */
    protected int lineType = LINETYPE_UNKNOWN;
    
    /** Flag to indicate that the object needs to be reprojected. */
    protected boolean needToRegenerate = true;

    /**
     * Space for an application to associate geometry with an
     * application object. This object can contain attribute
     * information about the geometry.
     *
     * _at_see #setAppObject
     * _at_see #getAppObject
     */
    protected Object appObject;

    /**
     * A flag to render this geometry visible.
     */
    protected boolean visible = true;

    /**
     * The Java 2D containing the Shape of the Graphic. There may be
     * an array of them, in case the graphic wraps around the earth,
     * and we need to show the other edge of the graphic on the other
     * side of the earth.
     */
    protected transient GeneralPath shape = null;

  //////////////////////////////////////////////////////////

    /**
     * Set the line type for the graphic, which will affect how the
     * lines will be drawn. See the definition of the lineType
     * parameter. Accepts LINETYPE_RHUMB, LINETYPE_STRAIGHT and
     * LINETYPE_GREATCIRCLE. Any weird values get set to
     * LINETYPE_STRAIGHT.
     *
     * _at_param value the line type of the graphic.
     * */
    public void setLineType(int value) {
        if (lineType == value) return;
        setNeedToRegenerate(true); // flag dirty
        
        lineType = value;
    }

    /**
     * Return the line type.
     *
     * _at_return the linetype - LINETYPE_RHUMB, LINETYPE_STRAIGHT,
     * LINETYPE_GREATCIRCLE or LINETYPE_UNKNOWN.
     */
    public int getLineType() {
        return lineType;
    }
    
    /**
     * Return the render type.
     *
     * _at_return the rendertype of the object - RENDERTYPE_LATLON,
     * RENDERTYPE_XY, RENDERTYPE_OFFSET and RENDERTYPE_UNKNOWN.
     */
    public abstract int getRenderType();
    
    /**
     * Sets the regenerate flag for the graphic.
     * This flag is used to determine if extra work needs to be done
     * to prepare the object for rendering.
     *
     * _at_param value boolean
     */
    public void setNeedToRegenerate(boolean value) {
        needToRegenerate = value;
        if (value == true) {
            shape = null;
        }
    }

    /**
     * Return the regeneration status.
     *
     * _at_return boolean
     */
    public boolean getNeedToRegenerate() {
        return needToRegenerate;
    }

    /**
     * Set the visibility variable.
     * NOTE:<br>
     * This is checked by the OMGeometryList when it iterates through its list
     * for render and gesturing. It is not checked by the internal OMGeometry
     * methods, although maybe it should be...
     *
     * _at_param visible boolean
     */
    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    /**
     * Get the visibility variable.
     *
     * _at_return boolean
     */
    public boolean isVisible() {
        return visible;
    }

    /**
     * Let the geometry object know it's selected. No action mandated.
     */
    public void select() {}

    /**
     * Let the geometry object know it's deselected. No action mandated.
     */
    public void deselect() {}

    /**
     * Holds an application specific object for later access.
     * This can be used to associate an application object with
     * an OMGeometry for later retrieval. For instance, when
     * the graphic is clicked on, the application gets the OMGeometry
     * object back from the OMGeometryList, and can then get back
     * to the application level object through this pointer.
     *
     * _at_param obj Object
     */
    public synchronized void setAppObject(Object obj) {
        appObject = obj;
    }

    /**
     * Gets the application's object pointer.
     *
     * _at_return Object
     */
    public synchronized Object getAppObject() {
        return appObject;
    }

//////////////////////////////////////////////////////////////////////////

    /**
     * Prepare the geometry for rendering.
     * This must be done before calling <code>render()</code>! If a
     * vector graphic has lat-lon components, then we project these
     * vertices into x-y space. For raster graphics we prepare in a
     * different fashion. <p>
     * If the generate is unsuccessful, it's usually because of some
     * oversight, (for instance if <code>proj</code> is null), and if
     * debugging is enabled, a message may be output to the controlling
     * terminal. <p>
     *
     * _at_param proj Projection
     * @return boolean true if successful, false if not.
     * _at_see #regenerate
     */
    public abstract boolean generate(Projection proj);

    /**
     *
     */
    public boolean isRenderable() {
        return (!getNeedToRegenerate() &&
                isVisible() && shape != null);
    }

    /**
     * Paint the graphic, as a filled shape. <P>
     *
     * This paints the graphic into the Graphics context. This is
     * similar to <code>paint()</code> function of
     * java.awt.Components. Note that if the graphic has not been
     * generated or if it isn't visible, it will not be
     * rendered. <P>
     *
     * This method used to be abstract, but with the conversion of
     * OMGeometrys to internally represent themselves as java.awt.Shape
     * objects, it's a more generic method. If the OMGeometry hasn't
     * been updated to use Shape objects, it should have its own
     * render method.
     *
     * _at_param g Graphics2D context to render into.
     */
    public void fill(Graphics g) {
        if (isRenderable()) {
            ((Graphics2D)g).fill(shape);
        }
    }
            
    /**
     * Paint the graphic, as an outlined shape. <P>
     *
     * This paints the graphic into the Graphics context. This is
     * similar to <code>paint()</code> function of
     * java.awt.Components. Note that if the graphic has not been
     * generated or if it isn't visible, it will not be
     * rendered. <P>
     *
     * This method used to be abstract, but with the conversion of
     * OMGeometrys to internally represent themselves as java.awt.Shape
     * objects, it's a more generic method. If the OMGeometry hasn't
     * been updated to use Shape objects, it should have its own
     * render method.
     *
     * _at_param g Graphics2D context to render into.
     */
    public void draw(Graphics g) {
        if (isRenderable()) {
            ((Graphics2D)g).draw(shape);
        }
    }

    /**
     * Return the shortest distance from the edge of a graphic to an
     * XY-point. <p>
     *
     * _at_param x X coordinate of the point.
     * @param y Y coordinate of the point.
     * _at_return float distance, in pixels, from graphic to the point.
     * Returns Float.POSITIVE_INFINITY if the graphic isn't ready
     * (ungenerated).
     */
    public float distanceToEdge(int x, int y) {
        float temp, distance = Float.POSITIVE_INFINITY;

        if (getNeedToRegenerate() || shape == null) {
            return distance;
        }

        PathIterator pi2 = shape.getPathIterator(null);
        FlatteningPathIterator pi = new FlatteningPathIterator(pi2, .25);
        double[] coords = new double[6];
        int count = 0;
        double startPntX = 0;
        double startPntY = 0;
        double endPntX = 0;
        double endPntY = 0;

        while (!pi.isDone()) {
            int type = pi.currentSegment(coords);
            float dist;

            if (type == PathIterator.SEG_LINETO) {
                startPntX = endPntX;
                startPntY = endPntY;
                endPntX = coords[0];
                endPntY = coords[1];

                dist = (float) Line2D.ptSegDist(startPntX, startPntY, endPntX, endPntY, (double)x, (double)y);

                if (dist < distance) {
                    distance = dist;
                }

                if (Debug.debugging("omgraphicdetail")) {
                    Debug.output("Type: " + type + "(" +
                                 (count++) + "), " +
                                 startPntX + ", " + startPntY + ", " +
                                 endPntX + ", " + endPntY + ", " +
                                 x + ", " + y +
                                 ", distance: " + distance);
                }
                    
            } else {

                // This should be the first and last
                // condition, SEG_MOVETO and SEG_CLOSE
                startPntX = coords[0];
                startPntY = coords[1];
                endPntX = coords[0];
                endPntY = coords[1];
            }

            pi.next();
        }

        return distance;
    }

    /**
     * Return the shortest distance from the graphic to an
     * XY-point. Checks to see of the point is contained within the
     * OMGraphic, which may, or may not be the right thing for clear
     * OMGraphics or lines.<p>
     *
     * This method used to be abstract, but with the conversion of
     * OMGeometrys to internally represent themselves as java.awt.Shape
     * objects, it's a more generic method. If the OMGeometry hasn't
     * been updated to use Shape objects, it should have its own
     * distance method.<p>
     *
     * Calls _distance(x, y);
     *
     * _at_param x X coordinate of the point.
     * _at_param y Y coordinate of the point.
     * _at_return float distance, in pixels, from graphic to the point.
     * Returns Float.POSITIVE_INFINITY if the graphic isn't ready
     * (ungenerated).
     */
    public float distance(int x, int y) {
        return _distance(x, y);
    }

    /**
     * Return the shortest distance from the graphic to an
     * XY-point. Checks to see of the point is contained within the
     * OMGraphic, which may, or may not be the right thing for clear
     * OMGraphics or lines.<p>
     *
     * _distance was added so subclasses could make this call if their
     * geometries/attributes require this action (when fill color
     * doesn't matter).
     *
     * _at_param x X coordinate of the point.
     * _at_param y Y coordinate of the point.
     * _at_return float distance, in pixels, from graphic to the point.
     * Returns Float.POSITIVE_INFINITY if the graphic isn't ready
     * (ungenerated).
     */
    protected float _distance(int x, int y) {
        float temp, distance = Float.POSITIVE_INFINITY;

        if (getNeedToRegenerate() || shape == null) {
            return distance;
        }

        if (shape.contains((double)x, (double)y)) {
// if (Debug.debugging("omgraphicdetail")) {
// Debug.output(" contains " + x + ", " + y);
// }
            return 0f;
        } else {
            return distanceToEdge(x, y);
        }
    }

    /**
     * Answsers the question whether or not the OMGeometry contains
     * the given pixel point.
     * <P>
     * This method used to be abstract, but with the conversion of
     * OMGeometrys to internally represent themselves as java.awt.Shape
     * objects, it's a more generic method. If the OMGeometry hasn't
     * been updated to use Shape objects, it should have its own
     * contains method.
     * <P>
     * This method duplicates a java.awt.Shape method, with some
     * protection wrapped around it. If you have other queries for
     * the internal Shape object, just ask for it and then ask it
     * directly. This method is provided because it is the most
     * useful, used when determining if a mouse event is occuring over
     * an object on the map.
     *
     * _at_param x X pixel coordinate of the point.
     * _at_param y Y pixel coordinate of the point.
     * _at_return getShape().contains(x, y), false if the OMGraphic
     * hasn't been generated yet.
     */
    public boolean contains(int x, int y) {
        Shape shape = getShape();
        boolean ret = false;

        if (shape != null) {
            ret = shape.contains((double)x, (double)y);
        }

        return ret;
    }

    /**
     * Invoke this to regenerate a "dirty" graphic.
     * This method is a wrapper around the <code>generate()</code>
     * method. It invokes <code>generate()</code> only if
     * </code>needToRegenerate()</code> on the graphic returns true.
     * To force a graphic to be generated, call
     * <code>generate()</code> directly.
     *
     * _at_param proj the Projection
     * @return true if generated, false if didn't do it (maybe a
     * problem).
     * _at_see #generate
     */
    public boolean regenerate(Projection proj) {
        if (proj == null) {
            return false;
        }

        if (getNeedToRegenerate()) {
            return generate(proj);
        }

        return false;
    }

    /**
     * Get the java.awt.Shape object that represents the projected
     * graphic. The array will one Shape object even if the object
     * wraps around the earth and needs to show up in more than one
     * place on the map. In conditions like that, the Shape will have
     * multiple parts.<p>
     *
     * The java.awt.Shape object gives you the ability to do a little
     * spatial analysis on the graphics.
     *
     * _at_return java.awt.geom.GeneralPath (a java.awt.Shape object), or
     * null if the graphic needs to be generated with the current map
     * projection, or null if the OMGeometry hasn't been updated to
     * use Shape objects for its internal representation.
     */
    public GeneralPath getShape() {
        return shape;
    }

    /**
     * Set the java.awt.Shape object that represents the projected
     * graphic. This Shape object should be internally generated, but
     * this method is provided to clear out the object to save memory,
     * or to allow a little customization if your requirements
     * dictate.<p>
     *
     * The java.awt.Shape object gives you the ability to do a little
     * spatial analysis on the graphics.
     *
     * _at_param gp java.awt.geom.GeneralPath, or null if the graphic
     * needs to be generated with the current map projection or to
     * clear out the object being held by the OMGeometry.
     */
    public void setShape(GeneralPath gp) {
        shape = gp;
    }

    /**
     * Create a Shape object given an array of x points and y points.
     * The x points a y points should be projected. This method is
     * used by subclasses that get projected coordinates out of the
     * projection classes, and they need to build a Shape object from
     * those coordinates.
     * _at_param xpoints projected x coordinates
     * _at_param ypoints projected y coordinates
     * _at_param isPolygon whether the points make up a polygon, or a
     * polyline. If it's true, the Shape object returned is a
     * Polygon. If false, the Shape returned is a GeneralPath object.
     * _at_return The Shape object for the points.
     */
    public static GeneralPath createShape(int xpoints[], int ypoints[], boolean isPolygon) {
        return createShape(xpoints, ypoints, 0, xpoints.length, isPolygon);
    }

    /**
     * Create a Shape object given an array of x points and y points.
     * The x points a y points should be projected. This method is
     * used by subclasses that get projected coordinates out of the
     * projection classes, and they need to build a Shape object from
     * those coordinates.
     * _at_param xpoints projected x coordinates
     * _at_param ypoints projected y coordinates
     * _at_param startIndex the starting coordinate index in the array.
     * _at_param length the number of points to use from the array for the shape.
     * _at_param isPolygon whether the points make up a polygon, or a
     * polyline. If it's true, the Shape object returned is a
     * Polygon. If false, the Shape returned is a GeneralPath object.
     * _at_return The Shape object for the points.
     */
    public static GeneralPath createShape(int xpoints[], int ypoints[],
                                          int startIndex, int length,
                                          boolean isPolygon) {
        // used to return a Shape

        if (xpoints == null || ypoints == null) {
            return null;
        }

        if (startIndex < 0) {
            startIndex = 0;
        }

        if (length > xpoints.length - startIndex) {
            // Do as much as you can...
            length = xpoints.length - startIndex - 1;
        }
        
        GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, length);
        
        if (length > startIndex) {
            path.moveTo(xpoints[startIndex], ypoints[startIndex]);
            for (int j = startIndex + 1; j < length; j++) {
                path.lineTo(xpoints[j], ypoints[j]);
            }
        
            if (isPolygon) {
                path.closePath();
            }
        }
        
        return path;
    }

    /**
     * Utility method that iterates over a Shape object and prints out the points.
     */
    public static void describeShapeDetail(Shape shape) {
        describeShapeDetail(shape, .25);
    }

    /**
     * Utility method that iterates over a Shape object and prints out
     * the points. The flattening is used for a
     * FlatteningPathIterator, controlling the scope of the path
     * traversal.
     */
    public static void describeShapeDetail(Shape shape, double flattening) {
        PathIterator pi2 = shape.getPathIterator(null);
        FlatteningPathIterator pi = new FlatteningPathIterator(pi2, flattening);
        double[] coords = new double[6];
        int pointCount = 0;

        Debug.output(" -- start describeShapeDetail with flattening[" + flattening + "]");
        while (!pi.isDone()) {
            int type = pi.currentSegment(coords);
            Debug.output(" Shape point [" + type + "] (" + (pointCount++) + ") " +
                         coords[0] + ", " + coords[1]);
            pi.next();
        }

        Debug.output(" -- end (" + pointCount + ")");
    }

    /**
     * Convenience method to add the coordinates to the given
     * GeneralPath. You need to close the path yourself if you want
     * it to be a polygon.
     * _at_param toShape the GeneralPath Shape object to add the coordinates to.
     * _at_param xpoints horizontal pixel coordiantes.
     * _at_param ypoints vertical pixel coordiantes.
     * _at_return toShape, with coordinates appended.
     */
    public static GeneralPath appendShapeEdge(GeneralPath toShape,
                                              int xpoints[], int ypoints[]) {
        return appendShapeEdge(toShape, xpoints, ypoints, 0, xpoints.length);
    }

    /**
     * Convenience method to add the coordinates to the given
     * GeneralPath. You need to close the path yourself if you want
     * it to be a polygon.
     * _at_param toShape the GeneralPath Shape object to add the coordinates to.
     * _at_param xpoints horizontal pixel coordiantes.
     * _at_param ypoints vertical pixel coordiantes.
     * _at_param startIndex the index into pixel coordinate array to start reading from.
     * _at_param length the number of coordinates to add.
     * _at_return toShape, with coordinates appended.
     */
    public static GeneralPath appendShapeEdge(GeneralPath toShape,
                                              int xpoints[], int ypoints[],
                                              int startIndex, int length) {
        return appendShapeEdge(toShape, createShape(xpoints, ypoints, startIndex, length, false));
    }

    /**
     * Convenience method to append the edge of a GeneralPath Shape to
     * another GeneralPath Shape. A PathIterator is used to figure
     * out the points to use to add to the toShape. You need to close
     * the path yourself if you want it to be a polygon.
     * _at_param toShape the GeneralPath Shape object to add the edge to.
     * _at_param addShape the GeneralPath Shape to add to the toShape.
     * @return toShape, with coordinates appended. Returns addShape
     * if toShape was null.
     */
    public static GeneralPath appendShapeEdge(GeneralPath toShape,
                                              GeneralPath addShape) {

        boolean DEBUG = Debug.debugging("arealist");
        int pointCount = 0;
        boolean firstPoint = true; //as: was false;
                                   // See ESRIPolygonRecord.addOMGraphics

        // If both null, return null.
        if (addShape == null) {
            return toShape;
        }

        if (toShape == null) {
            return addShape;
        }

        PathIterator pi2 = addShape.getPathIterator(null);
        FlatteningPathIterator pi = new FlatteningPathIterator(pi2, .25);
        double[] coords = new double[6];

        while (!pi.isDone()) {
            int type = pi.currentSegment(coords);
            if (firstPoint) {
                if (DEBUG) {
                    Debug.output("Creating new shape, first point " +
                                 (float)coords[0] + ", " + (float)coords[1]);
                }
                toShape.moveTo((float)coords[0], (float)coords[1]);
                firstPoint = false;
            } else {
                if (DEBUG) {
                    Debug.output(" adding point [" + type + "] (" + (pointCount++) + ") " +
                                 (float)coords[0] + ", " + (float)coords[1]);
                }
                toShape.lineTo((float)coords[0], (float)coords[1]);
            }
            pi.next();
        }

        if (DEBUG) {
            Debug.output(" -- end point (" + pointCount + ")");
        }

        return toShape;
    }


    /**
     * Create a general path from a point plus a height and width;
     */
    public static GeneralPath createBoxShape(int x, int y, int width, int height) {
        int[] xs = new int[4];
        int[] ys = new int[4];

        xs[0] = x;
        ys[0] = y;
        xs[1] = x + width;
        ys[1] = y;
        xs[2] = x + width;
        ys[2] = y + height;
        xs[3] = x;
        ys[3] = y + height;

        return createShape(xs, ys, true);
    }
}
--
[To unsubscribe to this list send an email to "majdart_at_bbn.com"
with the following text in the BODY of the message "unsubscribe openmap-users"]
Received on Fri Jun 25 2004 - 11:25:28 EDT

This archive was generated by hypermail 2.3.0 : Tue Mar 28 2017 - 23:25:05 EDT