Thursday, October 23, 2008

Harvesting Autodesk DWG Entities

Author: Jonio, Dennis

This kicks off a series of articles on building SDO_GEOMETRY types from DWG entities. By series end you should have at least one working NETLOADable module for a template. I do not pretend that this is THE solution. Since this was completed, I have, of course, realized better ways to do some things and I am sure you will be able to find more. I strongly recommend that you look over the Oracle OTN forums in particular .NET and Spatial. The heart and soul of these UDT’s came from there. Another indispensable forum is the Autodesk Discussion Groups. Their .NET forum is also indispensable.

Oracle SDO_GEOMETRY and a few others as User Defined Types (UDT’s)

Included below are a couple projects NetSdoGeometry and NetSdoDimArray. These are the fundamental classes and I have posted these in the past. However since that time I have added an AsText property and the ToString() method. It became necessary to actually visualize the objects during development. I must also point out that SDO_GEOMETRY is now marked as [Serializable]. You will see later on that this allows for the inclusion of this type into a native .NET serializable dataset. I have included a fair number of non-essential members and methods. They are for convenience so the purist may wish to strip them.

Triplets and Triplet Management

In order to make any sense of this article you will have to have a copy of Oracle’s SDO_GEOMETRY specification and my implementation of the class NetSdoGeometry.sdogeometry.

Let me clarify, in general terms, what this implementation does and does not do in regards the specification

  1. Is Limited to two(2) dimensional geometries. Points, lines, surfaces(polygons)
  2. Does not include “compound-type” geometries
  3. Does include support for curves/arcs

After reviewing the spec you will realize it is all about “triplets”. The SDO_DIM_ELEM_ARRAY is of course a 1-> n series of 3 element integers groups. OFFSET - ETYPE -INTERPRETATION as the named components.

The manipulation of the ordinates array I found to be rather trivial both in terms of composition and decomposition. It is nothing more than that a left to right series of numbers.

“Triple Management” becomes the focus. Since I was learning the spec and writing code to it as I went along, the first class I created was, of course, “Triplet”. This class helped me visualize and understand the key elements of the specification. It is nothing more than codifying the obvious. The only oddity is my use of a GUID to track external or internal rings. The idea here will be explained later. In most cases a triplet acts as a kind’a sort’a header. A header that explains the ordinate array that is part of the SDO_GEOMETRY class. It would be just that simple if all one wished to do was work with linestrings. It is the support for curves/arcs that really adds a level of complexity by using a series of triplets to denote the transitions from line to arc, arc to line, etc.

Class “Pts” is a simple container for holding the ordinates. Of note here is the BulgeValue member. This acts as the placeholder for the arc information gotten from, in my case Map3d.

Finally class “TripletMgr” or Triplet Manager if you will. Before I discuss this class I will give you a chance to see how it is used. I do not use it for points and simple lines. Really unnecessary in those cases. This code decomposes as MPolygon into an sdogeometry. Frankly if you understand this one the rest is easy.

Here is the basic idea:

  
public static sdogeometry PolygonGeometryFromMPolygon(MPolygon mpoly)
{
sdogeometry sdo = null;
Point2dCollection p2dColl = new Point2dCollection();
int hasBulgesCount;
int totalOrdinatesWritten = 0; // The total ordinates in the array
int totalSubElementCount = 0; // Just the total of sub-elements
int OWNERSHIP = 1; // Manage the OFFSET for a triplet
LoopDirection loopdirection;

TripletMgr MasterTripletMgr = new TripletMgr();
int loops = mpoly.NumMPolygonLoops;
for (int i = 0; i < loops; i++)
{
// Gather up points and bulges for this loop
MPolygonLoop mPolygonLoop = mpoly.GetMPolygonLoopAt(i);
loopdirection = mpoly.GetLoopDirection(i);
hasBulgesCount = 0;
// Remove duplicate verticies
for (int d = 1; d < mPolygonLoop.Count - 1; d++)
{
if (mPolygonLoop[d].Vertex.IsEqualTo(mPolygonLoop[d + 1].Vertex))
{
mPolygonLoop.RemoveAt(d + 1);
}
}
//
Point2d[] pts = new Point2d[mPolygonLoop.Count];
double[] blgs = new double[mPolygonLoop.Count];
for (int z = 0; z < mPolygonLoop.Count; z++)
{
if (z == mPolygonLoop.Count - 1)
{
pts[z] = pts[0];
}
else
{
double X = Math.Round(mPolygonLoop[z].Vertex.X, 10, MidpointRounding.AwayFromZero);
double Y = Math.Round(mPolygonLoop[z].Vertex.Y, 10, MidpointRounding.AwayFromZero);
pts[z] = new Point2d(X, Y);
}
blgs[z] = mPolygonLoop[z].Bulge;
if (blgs[z] != 0.0)
hasBulgesCount = hasBulgesCount + 1;
}
// dej 2008/12/17
// Some fixup for starting and/or ending with an arc in that the bulges get duplicated
// along with the vertices. This also seems to be a bigger issue with "Inner" vs "Outer"
// loops. Obviously if we have a series of arcs they won't have dup values anyway.
// We must not decrement hasBulgesCount!!!
if (blgs[0] == blgs[mPolygonLoop.Count - 1])
blgs[mPolygonLoop.Count - 1] = 0.0;

//
// Finished getting all the ordinates and bulges for this loop
//
// Just a dummy triplet to work with and begin the process of formulation
// Each loop needs its own header triplet.
// Establish the ETYPE and the OWNERSHIP(OFFSET) marker for this header
// and its INTERPRETATION
int headerOffset = (i == 0) ? 1 : OWNERSHIP;
Triplet dummy_headerTriplet = new Triplet(new int[] { headerOffset, 0, 0 });
if (hasBulgesCount == 0)
dummy_headerTriplet.INTERPRETATION = 1; // all "lines"
else if (hasBulgesCount == pts.Length)
dummy_headerTriplet.INTERPRETATION = 2; // all "arcs"
// if it is not a 1 or 2 we have to fill the INTERPRETATION value in later
// because it contains the number of subelements
if (loopdirection == LoopDirection.Exterior)
{
// It is an exterior
if (dummy_headerTriplet.INTERPRETATION == 1 || dummy_headerTriplet.INTERPRETATION == 2)
{
dummy_headerTriplet.ETYPE = 1003;
}
}
else
{
// It is an interior
if (dummy_headerTriplet.INTERPRETATION == 1 || dummy_headerTriplet.INTERPRETATION == 2)
{
dummy_headerTriplet.ETYPE = 2003;
}
}
// We have not set the etype yet so it must be a 1005 0R 2005 - compound polygon
// dej 2008/12/17 if (dummy_headerTriplet.ETYPE == 0)
if (dummy_headerTriplet.ETYPE == 0 || hasBulgesCount > 0)
{
if (loopdirection == LoopDirection.Exterior)
dummy_headerTriplet.ETYPE = 1005;
else
dummy_headerTriplet.ETYPE = 2005;
}
//
// Every time we make a transition from/to a curve/line we will make up a triplet
//
// We new-up a triplet mgr for these subelements within this loop
// We do know that the etype MUST be a 2 - line or arc
//
TripletMgr tmgr = new TripletMgr();
Triplet nxtTriplet = null;
int currentINTERPRETATION = 0;
int lastwrittenINTERPRETATION = 0;
Point2d somePointOnArc;
Point2d arc_endpoint;
for (int j = 0; j < mPolygonLoop.Count; j++)
{
double theBulge = blgs[j];
//Test the bulge
if (theBulge != 0.0)
{
// Look ahead to get the endpoint for the arc
if (j == (mPolygonLoop.Count - 1))
arc_endpoint = pts[0];
else
arc_endpoint = pts[j + 1];

CircularArc2d ca2d = new CircularArc2d(pts[j], arc_endpoint, theBulge, false);
//
// Get the center point on the arc ....
// As an alternative:/ Point2d[] somePointSOnArc = ca2d.GetSamplePoints(3);
// The somePointSOnArc[1] element will have the centerpoint on the arc
//
Interval interval_of_arc = ca2d.GetInterval();
somePointOnArc = ca2d.EvaluatePoint(interval_of_arc.Element / 2);
currentINTERPRETATION = 2;
}
else
{
currentINTERPRETATION = 1;
}
// This only happens once per loop/ring because nxtTriplet is no longer null
// We use this to start off the tracking transitions from-to curves/lines
if (nxtTriplet == null)
{
int elementOffset = (i == 0) ? 1 : OWNERSHIP;
nxtTriplet = new Triplet(new int[] { elementOffset, 2, currentINTERPRETATION });
lastwrittenINTERPRETATION = currentINTERPRETATION;
}
// Now collect the points and if we have a bulge add the midpoint only
// and not the endpoint that will be next
p2dColl.Add(pts[j]);
totalOrdinatesWritten = totalOrdinatesWritten + 2;
OWNERSHIP = OWNERSHIP + 2;
if (theBulge != 0.0)
{
p2dColl.Add(new Point2d(somePointOnArc.ToArray()));
totalOrdinatesWritten = totalOrdinatesWritten + 2;
OWNERSHIP = OWNERSHIP + 2;
// Oracle must be made happy.
// OK this is really tricky ... the next item may or may not have a bulge
// if the next item is at the end and it has a bulge this is a circle!
// If it does not it is a line and we must loop back up so we make the proper
// switch on interp and get the OWNERSHIP value correct. Otherwise
// our OWNERSHIP value will be off by 2
//
if ((j + 1) == (mPolygonLoop.Count - 1))
{
if (blgs[j + 1] != 0.0)
{
p2dColl.Add(pts[j + 1]);
totalOrdinatesWritten = totalOrdinatesWritten + 2;
OWNERSHIP = OWNERSHIP + 2;
j = j + 1;
}
}
}
//
// Now we construct an additional triplet in the event we have changed INTERPRETATION
//
if (currentINTERPRETATION != lastwrittenINTERPRETATION)
{
tmgr.TripletList.Add(nxtTriplet);
totalSubElementCount++;
//
// If I am transitioning to an arc/curve my ownership actually started
// 4 ordinates ago or 2 ordinates ago in the case of a line
//
int elementOffset = (currentINTERPRETATION == 2) ? (OWNERSHIP - 4) : OWNERSHIP - 2;
nxtTriplet = new Triplet(new int[] { elementOffset, 2, currentINTERPRETATION });
lastwrittenINTERPRETATION = currentINTERPRETATION;
}
} // end of for-loop on vertex count ... finished with one of the rings.
//
// Add this last triplet no matter what
//
tmgr.TripletList.Add(nxtTriplet);
totalSubElementCount++;
//
// If we are a 1005 or a 2005 we must fix-up the header triplet to reflect
// the total number of triplets(subelements) that are present. In this case it
// gets stuffed into the INTERP position.
// In the end the Master will hold the entire polygon
//
if (tmgr.TripletCount > 1)
{
dummy_headerTriplet.INTERPRETATION = totalSubElementCount;
MasterTripletMgr.TripletList.Add(dummy_headerTriplet);
for (int x = 0; x < tmgr.TripletCount; x++)
{
MasterTripletMgr.TripletList.Add(tmgr.TripletList[x]);
}
}
//
// If we are a 1003 or a 2003
//
if (tmgr.TripletCount == 1)
{
MasterTripletMgr.TripletList.Add(dummy_headerTriplet);
}
// Set for next ring ...
totalSubElementCount = 0;
}// for each loop in the mpolygon
//
// We should have the geometry for the entire MPolygon described
// Construct the sdo_geometry object
//
sdo = new sdogeometry();
sdo.Dimensionality = (int)sdogeometryTypes.DIMENSION.DIM2D;
sdo.ElemArrayOfInts = MasterTripletMgr.ElementInfoToArray;
double[] rtnval = new double[p2dColl.Count * 2];
for (int w = 0, cnt = 0; w < p2dColl.Count; w++)
{
rtnval[cnt++] = p2dColl[w].X; rtnval[cnt++] = p2dColl[w].Y;
}
sdo.OrdinatesArrayOfDoubles = rtnval;
sdo.GeometryType = (int)sdogeometryTypes.GTYPE.POLYGON;
sdo.PropertiesToGTYPE();
return sdo;
}

The method SetPts is the workhorse. Once again it is the management of the triplets. The concept of “ownership” can from Pro Oracle Spatial for Oracle Database 11g, by Kothuri, Godfrind, Beinat. This really helped me put some of the pieces together. I recommend it highly.

Source code (C#) available for download at:

No comments:

Post a Comment