Link to home
Start Free TrialLog in
Avatar of eelou
eelou

asked on

C# calculate acreage using meets and bounds

I wish to create a C# program that calculates acreage using meets and bounds, and, then create a graphical representation of the parcel of land.  I am not good with math, and I am fairly new to C#. For example here are the meets and bounds of a parcel of land&
      N 06° 27 11 E 488.27
      N 07° 01 06 W 347.57
      N 76° 50 13 E 447.76
      S 03° 47 57 E 205.31
      S 03° 14 01 E 224.48
      S 02° 58 30 E 111.0
      N 77° 18 15 W 129.23
      S 14° 55 4 W 519.65
      N 69° 10 01 W 232.85

Not expecting anyone to write the whole thing for me, if there are code examples of this then pointing me to it would be helpful.   How do I convert the meets and bounds to code, and then how do I present it graphically (I think that there is a polygon class in C#), and then how do I calculate the acreage?  How to detect if the diagram is not complete\closed (for example the information for the last line N69&, was not entered.  I am studying and playing with C# and ASP.NET.  An example in just c#, or ASP.NET using C#, would be good. Thanks.
      
Avatar of oobayly
oobayly
Flag of United Kingdom of Great Britain and Northern Ireland image

Have a look at the Microsoft SQL Server 2008 Feature Pack:
http://www.microsoft.com/downloads/details.aspx?familyid=B33D2C78-1059-4CE2-B80D-2343C099BCB4&displaylang=en

You could use the Microsoft.SqlServer.Types.SqlGeography class to create a region with the bounds given. From the SqlGeography object you can calculate the bounds.
Sorry, meant the SqlGeographyBuilder class. Here is a simple example that takes an array of points, and determines the area:
    private static double GetArea(PointF[] points) {
      SqlGeographyBuilder builder = new SqlGeographyBuilder();
      builder.SetSrid(4326); // 4326 is Spatial Reference Identifier for WGS84
      builder.BeginGeography(OpenGisGeographyType.Polygon);
 
      // Construct the Polygon from all the points
      builder.BeginFigure(points[0].Y, points[0].X);
      for (int i = 1; i < points.Length;i++){
        builder.AddLine(points[i].Y, points[i].X);
      }
 
      // Polygon's Start point must be equal to the end point
      if (points[0] != points[points.Length-1])
        builder.AddLine(points[0].Y, points[0].X);
 
      builder.EndFigure();
      builder.EndGeography();
 
      // Return the area
      return builder.ConstructedGeography.STArea().Value;
    }

Open in new window

Don't reinvent the wheel, use the new Sql Server 2008 geography datatypes to do the grunt work. Here I build a polygon out of points and calculated the area in metters all from built in methods.  Just use your own coordinates obviously
// Microsoft.SqlServer.Types
double[] latitudes = { -33, -31, -30, -33 };
double[] longitudes = { 151, 152, 152, 151 };
SqlGeographyBuilder gb = new SqlGeographyBuilder();
gb.SetSrid(4326);  // set spatial reference to WGS84
gb.BeginGeography(OpenGisGeographyType.Polygon);
gb.BeginFigure(latitudes[0], longitudes[0]); // start point
for (int i = 1; i < latitudes.Length; i++)
    gb.AddLine(latitudes[i], longitudes[i]);
gb.EndFigure();
gb.EndGeography();
SqlGeography g = gb.ConstructedGeography;
double squareMeters = g.STArea().Value;
double squareAcres = squareMeters * 0.000247105381;

Open in new window

bah see what happens when you walk away from your desk for a few minutes, you miss being first with the answer :)
I especially like the fact that our comments for SetSrid() are almost identical :-)
Avatar of eelou
eelou

ASKER

I really do not understand how I translate the coordinates that I gave as an example, into what the program is looking for.  One example above has hard coded arrays of longitudes and latitudes, the other does not.  Please show me an example of using 'N 06° 27' 11" E 488.27' in this program.
Well 'N 06° 27' 11" evaluates to 6.453055556
following the equation
decimal = degrees + minutes/60 + seconds/3600

E 488.27' seems to be in minutes so
decimal = 488.27 / 60 = 8.137833333
Though looking at your data, i believe you might have issues because the data crosses the hemisphere. That makes it an invlalid geography polygon with in Microsofts class. I'll try it later to see what I get.
Remember, South and West are negative when in decimal format.
The limitation isn't on an object crossing a hemisphere, but on objects being being too large to fit in a single hemisphere, regardless of it's orientation.
I can't post a link at the moment, but if you google sql geography hemisphere, and you'll find an informative blog by someone from MS.
Avatar of eelou

ASKER

The 488.27 is feet, not minutes.  I think that I have the information correct, I have attached the plot plan in the state of N.H. that this came from.  I use this plot plan as an example, I want anyone to be able to use any plot plan (that is measured in metes and bounds).  Ok, trying to understand this better, I would be accepting the user information from the screen and storing them in the arrays.  Where is the 'N' and 'E' in 'N 06° 27' 11" E 488.27' accounted for in the code?  Also, "minutes/60"... does the '27' 11"' equate to minutes and seconds (and do the seconds have to be handled), or feet and inches, or, when seen in metes and bounds, does ' stand for minutes and " stand for seconds?  If someone forgets to put in the last line of data, what will happen, and how is this to translate to acres?
IMG.jpg
Ah I see, those aren't latitude and longitude coordinates you gave us before but rather they are survey offsets.  Basically you have to do some leg work to calculate the lat/lon positions of each of the vertices of the plot to be able to use the SqlGeography examples above.  I'm not well versed on reading a survey, hopefully oobayly is better at it than I.
This is looking like an interesting challenge :-). can't give a proper solution at the moment, but I can picture how to do this.
The 1st thing to remember is that it doesn't matter what meridian you use, are as everything is relative. It's the latitude that's important This is probably why the data is given like it is. I've never come across data formatted like this, but that's not an issue.
What needs to be done is calculate the longitudinal distance that x feet is at a given latitude. this should be a piece of cake. then you will have a valid set of lat lon pairs to work with.
I'll post a follow up in the morning.
Avatar of eelou

ASKER

I am sure that you guys have a lot more experience with this stuff then I do, but are we talking apples and apples here, or apples and oranges?  Is SqlGeography going to be able to do what I want?  By entering in the metes and bounds from a survey or deed, are they going to be able to determine the acreage so that they can verify that they are getting the acreage that they were told that they buying?  Also, will I be able to generate an image that will be used to either verify the image that they have, or provide them with an image if they only have the metes and bounds information without an image?
If oobayly can convert it to a set of corridates you can use the SqlGeography to calculate the area, and yes with the SqlGeography type you will be able to overlay that shape on to virtual earth and actually see the shape right on the world where it belongs.  I'm not 100% but if i remember correctly with surveys, the textual description that goes with it is very important as well.
Finally got to look have a look at the image. What you've got are not Geodetic coordinates (ie WGS84), but a series of angles & distances. ie:
N69° 10' 01"W 232.85' (the bottom left measurement) means
From this point, the next point is 232.85 feet away at an angle of 69° 10' 01" to the West of (Magentic) North.

Likewise, S14° 55' 34" W 519.65' means
From this point the next point is 519.65 feet away at an angle of 14° 55' 34" to the West of (Magnetic) South.

Notice that none of the angles need to be greater than 90° using this method.

So, basically your code needs to "walk" around the plot just as the surveyor did. Instead of using the SqlGeography object (which is designed for Geodetic systems), you need to use the SqlGeometry object (which is designed for planar systems). As an added bonus the SqlGeometry isn't concerned as to whether the points are defined in a clockwise or anti-clockwise order.
As all the values will be in feet, the area will be returned in square feet, of which there are 43,560 square feet in an acres (as I'm sure you're now aware)
public static bool Test() {
  SurveySegment[] segs = new SurveySegment[]{
    new SurveySegment(){Degrees=06, Minutes=27, Seconds=11, Length=488.27, IsSouth=false, IsWest=false},
    new SurveySegment(){Degrees=07, Minutes=01, Seconds=06, Length=347.57, IsSouth=false, IsWest=true},
    new SurveySegment(){Degrees=76, Minutes=50, Seconds=13, Length=447.76, IsSouth=false, IsWest=false},
    new SurveySegment(){Degrees=03, Minutes=47, Seconds=57, Length=205.31, IsSouth=true, IsWest=false},
    new SurveySegment(){Degrees=03, Minutes=14, Seconds=01, Length=224.48, IsSouth=true, IsWest=false},
    new SurveySegment(){Degrees=02, Minutes=58, Seconds=30, Length=111.00, IsSouth=true, IsWest=false},
    new SurveySegment(){Degrees=77, Minutes=18, Seconds=15, Length=129.23, IsSouth=false, IsWest=true},
    new SurveySegment(){Degrees=14, Minutes=55, Seconds=04, Length=519.65, IsSouth=true, IsWest=true},
    new SurveySegment(){Degrees=69, Minutes=10, Seconds=01, Length=232.85, IsSouth=false, IsWest=true}
  };
  double area = GetArea(segs);
}
 
private static double GetArea(SurveySegment[] segments) {
  SqlGeometryBuilder builder = new SqlGeometryBuilder();
  builder.SetSrid(0); // Don't need an SRID here
  builder.BeginGeometry(OpenGisGeometryType.Polygon);
 
  // Walk around the segments
  PointF p = new PointF(0, 0);
  builder.BeginFigure(p.X, p.Y);
  for (int i = 0; i < segments.Length; i++) {
    p += segments[i].FromOrigin;
    builder.AddLine(p.X, p.Y);
  }
 
  // Polygon's end point must be at the origin
  if (p.X != 0 || p.Y != 0)
    builder.AddLine(0, 0);
 
  builder.EndFigure();
  builder.EndGeometry();
 
  // Return the area in the units used in the segments
  return builder.ConstructedGeometry.STArea().Value;
}
 
public struct SurveySegment {
  public byte Degrees, Minutes, Seconds;
  public bool IsSouth, IsWest;
  public double Length;
 
  public double DecimalDegrees {
    get {
      double angle = Degrees + ((double)Minutes / 60) + ((double)Seconds / 3600);
 
      if (IsSouth) {
        // If to the East & looking South, the angle is negative
        if (!IsWest) angle *= -1;
 
        // If looking South, add 180°
        angle += 180;
 
      } else {
        // If to the West & looking North, the angle is negative
        if (IsWest) angle *= -1;
      }
 
      // Only use positive angles (0° <= angle < 360°)
      return (angle + 360) % 360;
    }
  }
 
  public SizeF FromOrigin {
    get {
      /* The Y value is inverted so this can be rendered straight
       * to a GDI object.
       * ie. Left (East) is positive, Down (South) is positive
       */
      double angle = DecimalDegrees * Math.PI / 180;
      return new SizeF(
        (float)(Length * Math.Sin(angle)),
        -(float)(Length * Math.Cos(angle))
        );
    }
  }
}

Open in new window

Avatar of eelou

ASKER


This is good  (after I figured out the references and using statements that I needed), it does calculate the acreage.  I am thankful for your help.  What about the other half of my question "create a graphical representation of the parcel of land"? Does SqlGeometryBuilder not have this ability and SqlGeographyBuilder does, or is this a separate issue from these tools?  I am using a console application, do I need a windows form application to do the graphics part of this, and, how do I translate the 'angles and distances' into whatever the drawing tool that is to be used, needs?
Here's a project for a windows form to view SqlGeometries
http://www.codeproject.com/KB/database/sqlgeometry.aspx
if you have a reference point on the suvrvey to maps to earth cooridinates (latitude and longitude), you can use the angle and distances to calculate the coordinates for each vertix.  Just like oobayly did (nice job btw) you just need a starting point. Then once you have all the coordinates you can use the virtual earth api or google maps api to add the shape to a web map.

Heres a nice virtual earth adk site with code examples
http://dev.live.com/virtualearth/sdk/#
Avatar of eelou

ASKER

For someone who is new to C#, does not know SQLGeometries, this looks way out of my league.
Take a look at that code project link I think it will do 90% of what you want.  You could use ooblay's code to create a geometery from the angle and offsets as input by the user. Store that into a sql database. Then just using the code project tool, you could display the geometry.

Anyhow once you define if you definitely want a windows or web app, I would just post a new question at that point.

For now just try to understand ooblay's code and how you can create an application to accept user input and build the geometry from that input.
Avatar of eelou

ASKER

Maybe in a few weeks I might figure this out, right now it is pure Greek to me. In the meantime I should close this question.  Being that it is half completed my thought is that it is worth 250 points.  I am not really sure how to divide this up between the two of you.  Oobayly did come up with the code for the first half of the problem, should I give him all 250, or give wht1986 some of this?
Rendering the Geometry isn't difficult at all, you can create an array of Points from the Geomtery object (or whilst creating it. Then use the Graphics.DrawPolygon method to draw it.
You'd need to work out the bounds & transforms to make sure the polygon fits on the bitmap being created. But to be honest creating Image Transformation matrices are a topic in their own right.

This is the reason that my code inverts the Y-axis in the survey segment so that it's orientated correctly for rendering on a GDI object.

Instead of returning just the Area in the GetArea method, return the ConstructedGeometry object instead.
SqlGeometry geom = GetArea(...);
 
double area = geom.STArea().Value;
 
PointF[] points = new PointF[geom.STNumPoints()];
for (int i = 0; i < points.Length; i++){
  SqlGeometry g = geom.STPointN(i + 1);
  points[i] = new PointF(g.STX, g.STY);
}
 
Bitmap bmp = new Bitmap(1000,1000);
Graphics g = Graphics.FromImage(g);
g.DrawPolygon(Pens.Black, points);

Open in new window

He did the majority of the grunt work and did develop the code to walk around the property and build the geometery object. Of course I'd take a couple of points if you thought I contributed to your  understanding of the techniques and issues at hand through my links and code examples I provided.
Avatar of eelou

ASKER

I don't mind, and even look forward to trying to figure things out for myself, but this is beyond me.
I have been trying to adapt what you last put here, to my code(see attached).  I am still using a console application, is that possibly the problem?

First I had to change the GetArea function from double to SqlGeometry to get the first line of this to work.  Then I had to add the cast (int), to the new Pointf line.  I have not yet tried the other errors.

 
errors.doc
SOLUTION
Avatar of wht1986
wht1986
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of eelou

ASKER

I do very much appreciate the effort and the example that you have provided, it does help with the learning curve.  I just need to get the image part completed.  Thanks
Avatar of eelou

ASKER

wht1986.  I took your code and am trying to incorporate the code from oobayly into it...having problems, see attached.  Again I had to use casts (not sure that I should be doing this).
Doc2.doc
Oops, that should be
Graphics g = Graphics.FromImage(bmp);

also, you'll probably have to add the following, as you could be getting an all-black bitmap
g.Clear(Color.White);

Another also; as you're filling a point array, cast to a float rather than an int, you should get better rounding. Though you probably won't notice a pixel here or there, it's good practice.
Avatar of eelou

ASKER

As you can see from the attached, using float gives errors...how to handle this?

Also, when I execute the 'gr.DrawPolygon(Pens.Black, points);', what is supposed to happen, where?
Does this not work on a form, do I need to put something on the form for this, should this be done in a console application?
Doc3.doc
The following method will take a geometry object and draw it onto a bitmap object. Once you've got that bitmap you can save, set is as the image for a PictureBox on a Windows Form etc.

To be quite honest, you didn't half ask for a lot of things in this question.
How to transfer the plot into a set of geographic points
How to calculate the area once you've got the points
How to render the points to an image
How to display the image once you've created it.
Can I suggest you break a question of this magnitude into several parts in the future.

As wht1986 suggested, take your time and have a good look out how our code works. If you get to points you don't understand have a look in the MSDN documentation (they really are very helpful).
private void Foo(){
  SqlGeometry geom = GetGeometry(segs);
  double area = geom.STArea().Value;
  Image img = GetImage(geom, "ft");
}
 
public static Image GetImage(SqlGeometry geom, string units) {
  float minX = float.MaxValue;
  float maxX = float.MinValue;
  float minY = float.MaxValue;
  float maxY = float.MinValue;
 
  // Populate the points in the array
  PointF[] ps = new PointF[geom.STNumPoints().Value];
  for (int i = 0; i < ps.Length; i++) {
    SqlGeometry temp = geom.STPointN(i + 1);
 
    // Create the point in the array
    float x = (float)temp.STX.Value;
    float y = (float)temp.STY.Value;
    ps[i] = new PointF(x, y);
 
    // Get the bounds
    if (x < minX) minX = x;
    if (x > maxX) maxX = x;
    if (y < minY) minY = y;
    if (y > maxY) maxY = y;
  }
 
  // Create the bitmap to contain the bounds
  // Also use a 10px border
  int border = 10;
  Bitmap bmp = new Bitmap(
    (2 * border) + Convert.ToInt32(maxX - minX),
    (2 * border) + Convert.ToInt32(maxY - minY));
  Graphics g = Graphics.FromImage(bmp);
  g.Clear(Color.White);
 
  // Offset the points so that they fit on the image
  Matrix transform = new Matrix();
  transform.Translate(-minX + border, -minY + border);
  transform.TransformPoints(ps);
 
  // Draw the points
  g.DrawPolygon(new Pen(Color.Black, 2), ps);
 
  // Annotate the info on the centroid
  SqlGeometry centroid = geom.STCentroid();
  PointF[] cs = new PointF[] { 
    new PointF((float)centroid.STX.Value, (float)centroid.STY.Value)
  };
  transform.TransformPoints(cs);
 
  Font font = new Font("Arial", 10, FontStyle.Regular);
  g.DrawString(
    string.Format("Area: {0:N0}{2}²\nPerimeter: {1:N0}{2}", geom.STArea().Value, geom.STLength().Value, units),
    font, Brushes.Black, cs[0]);
 
  // Tidy up & return the image
  g.Dispose();
  return bmp;
}

Open in new window

Avatar of eelou

ASKER

You are correct in that I did not ask a number of questions.  I had no idea of the scope of the project, and thus had no idea of what to ask...and on top of that, there is my newness to all this.  For the sake of less embarrassment to myself, I will try to scope things out better in the future.  I am trying to look at all the things that the two of you have given me, and trying to see what\where 'SqlGeometry geom = GetGeometry(segs);' references (from what you recently gave me)   Specifically  'GetGeometry'.
Is this another name for a function that you have already given me, a namespace that I need to add to the project, one of the questions that I did not ask and need to figure out for myself, or ???  Sould this be a console, WFP windows form project?  Thanks
sorry, I renamed the method to reflect the change in what it returns. As for what type of application, I'd recommend a windows forms app is it gives you flexibility to accept user input. It also makes it easy to use a picturebox to display the rendered survey bitmap
Avatar of eelou

ASKER

I have tried to use what you have given me, but keep getting a error when trying to 'SqlGeometry geom = GetArea(segs);', see attached.
Doc4.doc
In your post you said that you changed GetArea to return an SqlGeometry:
https://www.experts-exchange.com/Programming/Languages/C_Sharp
/Q_24345988.html?cid=1066#a24218355

In the screenshot you've provided GetArea is still returning a double. It's signature should look like this, obviouslyyou'll have to alter the return statement too:
private static SqlGeometry GetArea(SurveySegment[] segments){
}

Sorry if this sounds harsh, but wht1986 and myself have provided you the code to accept and store input, calculate the survey properties and render the generated SqlGeometry. It's your job to put it all together into a workable solution. The errors you've posted are ones that can be understood by just reading the message. I find the easiest way to work with types I've never used before is to use the Intellisense in VS.

For example, the error "cannot convert from 'System.Data.SqlTypes.SqlDouble' to 'float'. I tried a cast to float first. Then I checked what properties are in an SqlDouble object. Intellisense told me that there's a property called Value which has a type of 'double'. This I can cast to float.

For your last post. The message "cannot implicilty convert 'double' to 'Microsoft..SqlServer.Types.SqlGeometry'. This tells you that you're expecting an SqlGeometry object back, but the method is only returning a double. Solution, change the signature of the method (like I did above) as well as the object being returned

I appreciate that you're new to this, but the errors you're receiving here aren't really anything to do with this question, but are general programming errors. Before you jump into a project like this you really need to know how to interpret compile errors & runtime Exceptions, what the effects of casting types are etc.

A simple way of deciding what time of project to create is this:
Is this a program that accepts user input in a friendly manner and needs to display data in a graphical manner? Then use Windows Forms.
Is this a program that will run automated tasks, ie. converting bulk data from once format to another, or sending automated emails at regular intervals. Then use a Console Application.
Avatar of eelou

ASKER

I do no mind constructive criticism, I am not sure that that is what I am getting.  While new to C#, and sqlgeometrybuilder, etc, I am not a newbie to either programming, and am able to work my way around Visual Studio, and do know how to use intellisense.

I recognized that the screen output that I had sent last time did not match what I was saying (after I sent it...had had tried a type change to double, but obviously not where it was needed).    The last time I had changed a type, you pointed out to me that I might end up missing some pixels.
Not knowing what I can change without screwing up what you gave me, I do try a number of things before I go crying back to you.  I am not sure that by change types here and there, that I am not screwing something up related to sqlgeometry.    So now we have changed the definition of the GetArea function and have got past that point.  Now there is another problem with the return statement from the GetArea, and again I am not sure what I should do to get around this.  The 'Value' is a double, and cannot be converted to sqlgeometry.  I have tried explicit casts to sqlgeometry, but this cannot be done with a double.  OK...is 'Value' not the correct thing to use, and if so, what should I change it to?  And if I get past this one, what else needs to be changed?  Does something need to be changed in 'public struct SurveySegment', and what problems will that cause?  What the heck is the big deal about ensuring that what you give out works?
Doc5.doc
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Note, the "hook" at the bottom left corner of the plot isn't a rendering error, but is a result of the start and end points not being identical. 3ft out after half a mile isn't too bad for a quick survey around a site.
Avatar of eelou

ASKER

I actually had changed the 'Return' statement to what you have just presented, but had not realized and\or
had forgotten that I also had to change the definition of the method also (but then again, I was worried about changing things so that things would not work correctly anymore).  If this the link that I need to download the CSML.dll\Matrix from, after first becoming a member of the site?...http://www.codeproject.com/KB/cs/CSML.aspx?display=Print.  It is the only thing that I have found so far that seems to relate to matrix  (I am missing a using or assembly reference)?
Nope, Matrix is part of the System.Drawing.Drawing2D namespace. It's not the most intuitive thing to work with as the order in which you perform transformation operations is very important. I tend to have to go through several a4 pages of sketches to work out the transform I want to create before I've any chance of getting it to work.
The 2nd link gives some information on how it works.
http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.aspx
http://msdn.microsoft.com/en-us/library/aa289165.aspx
Avatar of eelou

ASKER

Ok, that does it.  I assume that you write the image to a file instead of trying to display it because there would be no (easy), way to estimate how large the image would be (to ensure that it would fit into an already declared picturebox, etc?
I only wrote the image to a file as I test my code in a Console App. Using a PictureBox, you can set it's SizeMode property to Zoom. This way it will scale the image to fit in the bounds of the control.
Otherwise you could scale the size of the rendered bitmap, and also scale the transformation matrix too. For the moment, I'd just go with use the SizeMode property of the PictureBox until you get to grips with the transformation matrix
Avatar of eelou

ASKER

I hope that this will be the last time that I have to bother you on this.  Yes, the zoom works fine...the problem is that the area text information in the middle of the image is too small to read.  I tried a ' string sizeInfo = (string)centroid' so that I could display this info in a separate text box, but I could not convert the sqlgeometry to string (and I am not sure that this is the information that I want).

Also, it being time to close this and award the points, and, wanting to be correct and fair in this...what do you think about 350 to you and 150 to wht1986/
The area text information is the 1st parameter passed to DrawString(), centroid is the SqlGeometry object for the point at the centre of gravity of the reqgion. All you need is the following:
string areaText = string.Format("Area: {0:N0}{2}²\nPerimeter: {1:N0}{2}", geom.STArea().Value, geom.STLength().Value, units);

For the text information to be rendered visibly on the image, you'll need to scale the image using transforms.

How you award the points is up to you entirely, and is based on how you feel people have helped you.
Avatar of eelou

ASKER

I had no idea of the scope of the project, and, most of it was new to me.
I do appreciate the help that was given me.