Last week we talked about bulk operations for COGO Points. One of those operations is “point renumbering”. Like all bulk operations, there is an interface defined directly in the ‘CogoPoint’ object, but there is a more efficient way to do it through the bulk operation defined in the ‘CogoPointCollection’.
So, why talk about renumbering points if it is just another bulk operation? The quick answer is that renumbering points can get you into trouble very quickly. In an ideal world, you will create points and let Civil 3D do its stuff. Unfortunately, the world is less than ideal sometimes, so you have to plan how to work with the points you add.
Define Requirements for Point Numbers
In COGO Point Basics, I created a command “CDS_CreateRandomPoints”, which uses all the different ways in which you can create COGO Points. Let’s say that you run that command. The points the command creates should look similar to this:
As you can in the next screenshot, the points are assigned numbers sequentially.
The problem is Civil 3D customers do not work this way with points. Some customers use point numbers to inject special meanings. You may have requirements about what the point number should be depending on what the point represents, or you may have the requirement that a point added should have a specific number because it was the number assigned by the surveyor that initially measured the point. Bottom line, there will be instances when you will need to renumber a point, and when that happens, you to plan in advance.
Let’s say we implement a “naïve” command to renumber a point. The command “CDS_NaiveRenumberPoint” shows what most people will try to do.
C#
[CommandMethod("CDS_NaiveRenumberPoint")]
public void CDS_NaiveRenumberPoint()
{
PromptIntegerResult result = _editor.GetInteger(
"\nEnter point to renumber:");
if (result.Status != PromptStatus.OK)
{
return;
}
uint currentPointNumber = (uint)result.Value;
result = _editor.GetInteger("\nEnter new point number:");
if (result.Status != PromptStatus.OK)
{
return;
}
uint newPointNumber = (uint)result.Value;
CogoPointCollection points = _civildoc.CogoPoints;
ObjectId pointId = points.GetPointByPointNumber(currentPointNumber);
points.SetPointNumber(pointId, newPointNumber);
}
VB.NET
<CommandMethod("CDS_NaiveRenumberPoint")> _
Public Sub CDS_NaiveRenumberPoint()
Dim result As PromptIntegerResult = _editor.GetInteger(
vbLf & "Enter point to renumber:")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim currentPointNumber As UInteger = CUInt(result.Value)
result = _editor.GetInteger(vbLf & "Enter new point number:")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim newPointNumber As UInteger = CUInt(result.Value)
Dim points As CogoPointCollection = _civildoc.CogoPoints
Dim pointId As ObjectId = points.GetPointByPointNumber(
currentPointNumber)
points.SetPointNumber(pointId, newPointNumber)
End Sub
This code works as long as the point number entered by the user is not assigned yet. But run the command and assign a number that already exists. You will get the following dialog:
Before you start the celebrations because you just inherited a Civil 3D feature without writing a single line of code to make it work, think about the problem. If your application is UI based and all you are doing is renumbering the point, I guess is OK to let the dialog pop. But if your application’s intention is to automate some task and there should not be any user interaction, you are in trouble. (Note: To be honest, this is a bug, and we are working very hard to deliver a fix for this problem).
It is impossible for me to decide which course of action you should follow without knowing your requirements, but I will rewrite the command as “CDS_RenumberPoint” making the assumption that if the specified number is in use, we can use it as a hint and assign the next available point number. First, let’s take a look at the command changes:
C#
[CommandMethod("CDS_RenumberPoint")]
public void CDS_RenumberPoint()
{
PromptIntegerResult result = _editor.GetInteger(
"\nEnter point to renumber:");
if (result.Status != PromptStatus.OK)
{
return;
}
uint currentPointNumber = (uint)result.Value;
result = _editor.GetInteger("\nEnter new point number (hint):");
if (result.Status != PromptStatus.OK)
{
return;
}
uint pointNumberHint = (uint)result.Value;
try
{
CogoPointCollection points = _civildoc.CogoPoints;
ObjectId pointId =
points.GetPointByPointNumber(currentPointNumber);
points.SetPointNumber(pointId, getNextPointNumberAvailable(pointNumberHint));
}
catch (ArgumentException ex)
{
_editor.WriteMessage(ex.Message);
}
}
VB.NET
<CommandMethod("CDS_RenumberPoint")> _
Public Sub CDS_RenumberPoint()
Dim result As PromptIntegerResult = _editor.GetInteger(
vbLf & "Enter point to renumber:")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim currentPointNumber As UInteger = CUInt(result.Value)
result = _editor.GetInteger(vbLf & "Enter new point number (hint):")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim pointNumberHint As UInteger = CUInt(result.Value)
Try
Dim points As CogoPointCollection = _civildoc.CogoPoints
Dim pointId As ObjectId =
points.GetPointByPointNumber(currentPointNumber)
points.SetPointNumber(pointId,
getNextPointNumberAvailable(pointNumberHint))
Catch ex As ArgumentException
_editor.WriteMessage(ex.Message)
End Try
End Sub
The new implementation is similar. The main difference is that instead of specifying the number entered by the user, we call ‘getNextPointNumberAvailable’ to get the new point number using the value entered by the user as a hint. We surround the calls in a try/catch block because, as we will see in the implementation of ‘getNextPointNumberAvailable’ there might be times when we could not provide a new point. Let’s look at the implementation of that function.
C#
private uint getNextPointNumberAvailable(uint hint)
{
uint suggested = hint;
CogoPointCollection points = _civildoc.CogoPoints;
while (points.Contains(suggested) && suggested < _maxPointNumber)
{
suggested++;
}
if (suggested == _maxPointNumber)
{
string msg = String.Format(
"No available point number at {0} or greater value.",
hint);
throw new ArgumentException(msg);
}
return suggested;
}
VB.NET
Private Function getNextPointNumberAvailable(hint As UInteger) _
As UInteger
Dim suggested As UInteger = hint
Dim points As CogoPointCollection = _civildoc.CogoPoints
While points.Contains(suggested) AndAlso suggested < _maxPointNumber
suggested += 1
End While
If suggested = _maxPointNumber Then
Dim msg As String = [String].Format(
"No available point number at {0} or greater value.", hint)
Throw New ArgumentException(msg)
End If
Return suggested
End Function
The number passed to the function is used as a hint. If the point number is in use, we increment its value until we find an available point number. Once in a while, hell will break loose, and there would not be any more point numbers available starting at the value passed, in which case, we will hit the value of _maxPointNumber (this is really the same value as UInt32.MaxValue). When this happens, we will throw an exception. Otherwise, we will return the next available point number.
Renumber Points as Close to Creation as Possible
If your application embeds meaning into a COGO Point number, update/renumber the point as close to where is created as possible. Avoid implementing applications that manipulate point numbers after the fact because you most certainly will run into problems; worse yet, your customers will run into problems.
Even when you plan for a strategy to renumber points, there might be instances where things will not work the way you intended. These are more likely to happen when you allow users to specify parameters involved with point renumbering. Let’s implement a command that allows a user to specify the number of points created and a base number for the point number values assigned. The command looks something like this.
C#
[CommandMethod("CDS_CreateRandomPointsAtSpecifiedNumber")]
public void CDS_CreateRandomPointsAtSpecifiedNumber()
{
PromptIntegerResult result = _editor.GetInteger(
"\nEnter number of points to generate: ");
if (result.Status != PromptStatus.OK)
{
return;
}
int numberOfPoints = result.Value;
result = _editor.GetInteger(
"\nEnter base number for first point: ");
if (result.Status != PromptStatus.OK)
{
return;
}
uint basePoint = (uint)result.Value;
ObjectIdCollection createdIds = createPoints(numberOfPoints);
renumberPoints(createdIds, basePoint);
}
private ObjectIdCollection createPoints(int numberOfPoints)
{
RandomCoordinateGenerator generator =
new RandomCoordinateGenerator();
Point3dCollection coordinates =
generator.GetCoordinates(numberOfPoints);
CogoPointCollection points = _civildoc.CogoPoints;
_creationSet++;
string description = String.Format("Creation {0}", _creationSet);
return points.Add(coordinates, description);
}
private void renumberPoints(ObjectIdCollection pointIds, uint basePoint)
{
CogoPointCollection points = _civildoc.CogoPoints;
uint suggested = basePoint;
foreach (ObjectId pointId in pointIds)
{
suggested = getNextPointNumberAvailable(suggested);
points.SetPointNumber(pointId, suggested);
suggested++;
}
}
<CommandMethod("CDS_CreateRandomPointsAtSpecifiedNumber")> _
Public Sub CDS_CreateRandomPointsAtSpecifiedNumber()
Dim result As PromptIntegerResult = _editor.GetInteger(
vbLf & "Enter number of points to generate: ")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim numberOfPoints As Integer = result.Value
result = _editor.GetInteger(
vbLf & "Enter base number for first point: ")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim basePoint As UInteger = CUInt(result.Value)
Dim createdIds As ObjectIdCollection = createPoints(numberOfPoints)
renumberPoints(createdIds, basePoint)
End Sub
Private Function createPoints(numberOfPoints As Integer) _
As ObjectIdCollection
Dim generator As New RandomCoordinateGenerator()
Dim coordinates As Point3dCollection =
generator.GetCoordinates(numberOfPoints)
Dim points As CogoPointCollection = _civildoc.CogoPoints
_creationSet += 1
Dim description As String = [String].Format(
"Creation {0}", _creationSet)
Return points.Add(coordinates, description)
End Function
Private Sub renumberPoints(pointIds As ObjectIdCollection,
basePoint As UInteger)
Dim points As CogoPointCollection = _civildoc.CogoPoints
Dim suggested As UInteger = basePoint
For Each pointId As ObjectId In pointIds
suggested = getNextPointNumberAvailable(suggested)
points.SetPointNumber(pointId, suggested)
suggested += 1
Next
End Sub
As you can see, we prompt the user to enter the number of points to create and the base number to use when creating the points. At the end of the command, we see this is a two step operation. We first create the points, and then we renumber them.
Go ahead and run the command. I used ‘100’ for the number of points and ‘5’ for the base number to use.
Is this the result that you expected?
Probably not. The first point gets a number of ‘101’ and the rest follow sequentially. If you look at the implementation of ‘renumberPoints()’, you might be able to determine why. We are first creating the points, in this case 100 of them, and they are being numbered (assuming you started with a clean drawing) from 1 to 100. Therefore, when we renumber them with a base value of ‘5’, the point number is in use; therefore, the next available number ‘101’ is assigned.
Not a solution, but an alternate implementation can use a second overload of the ‘CogoPointCollection.SetPointNumber(). which takes an additive factor. Assuming, you know the number of the first point you added, you can add/subtract a factor to get to the value where you want to renumber. An implementation of this concept can be found in the command ‘CDS_CreateRandomPointsAtSpecifiedNumberByFactor’.
C#
[CommandMethod("CDS_CreateRandomPointsAtSpecifiedNumberByFactor")]
public void CDS_CreateRandomPointsAtSpecifiedNumberByFactor()
{
PromptIntegerResult result = _editor.GetInteger(
"\nEnter number of points to generate: ");
if (result.Status != PromptStatus.OK)
{
return;
}
int numberOfPoints = result.Value;
result = _editor.GetInteger(
"\nEnter base number for first point: ");
if (result.Status != PromptStatus.OK)
{
return;
}
uint basePoint = (uint)result.Value;
ObjectIdCollection createdIds = createPoints(numberOfPoints);
uint firstCreatedPointNumber = getPointNumberFor(createdIds[0]);
int additiveFactor = (int)(basePoint - firstCreatedPointNumber);
CogoPointCollection points = _civildoc.CogoPoints;
points.SetPointNumber(ToEnumerable(createdIds), additiveFactor);
}
VB.NET
<CommandMethod("CDS_CreateRandomPointsAtSpecifiedNumberByFactor")> _
Public Sub CDS_CreateRandomPointsAtSpecifiedNumberByFactor()
Dim result As PromptIntegerResult = _editor.GetInteger(
vbLf & "Enter number of points to generate: ")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim numberOfPoints As Integer = result.Value
result = _editor.GetInteger(
vbLf & "Enter base number for first point: ")
If result.Status <> PromptStatus.OK Then
Return
End If
Dim basePoint As UInteger = CUInt(result.Value)
Dim createdIds As ObjectIdCollection = createPoints(numberOfPoints)
Dim firstCreatedPointNumber As UInteger =
getPointNumberFor(createdIds(0))
Dim additiveFactor As Integer =
CInt(basePoint - firstCreatedPointNumber)
Dim points As CogoPointCollection = _civildoc.CogoPoints
points.SetPointNumber(ToEnumerable(createdIds), additiveFactor)
End Sub
As you can see, renumbering points is not a trivial task and you have to be very careful. Define your rules and give you some buffer. Try to abstract the task from your users or let Civil 3D do its thing. Of course, you can always play more with this part of the API, and you can download the complete source code from the Civilized Development repository.
hello Mr.Isaac, I am interested of using the Tangent Intesection labels apply in C3D 2012, since I want to used my previously custom style in my current drawing is it still possible through VB.net?
Posted by: zeus | 05/29/2012 at 10:34 AM
@zeus,
Unfortunately, I do not have the bandwidth to provide support for specific issues through this blog. It is better to seek support through the regular channels (ADN) where your request will be attended in a timely manner.
I am not sure I understand exactly what it is that you want to accomplish, but I should say that any functionality is working in 2012 should be working in later versions. Also, VB.NET is completely supported in the Civil 3D API.
Posted by: Isaac Rodriguez | 05/29/2012 at 10:51 AM
Hello Mr.Isaac;
Do you plan to explain Point styles, Point groups and Description keys in the blog. I tried to create this and it works ok, but there is a problem with deleting (I can delete point style, but can't create new with same name - reports that already exist), and Point group does not accept Description property setting.
Thanks;
Posted by: Robert | 05/30/2012 at 10:43 PM
Hi Robert,
I am planning to talk about Description Keys and Point Groups, so if you send me more information about your issues, I will try to cover them.
If you have some code that reproduces the problem, it would help me to research the issues you are having.
Posted by: Isaac Rodriguez | 05/31/2012 at 03:54 AM