ThinkGeo.com    |     Blog    |     Wiki    |     Support

Is Buffer broken?

I have data stored in geographic coordinates.  I’m attempting to create a buffer using ThinkGeo Map Suite Services Edition 9.0.  If I start with a MultiLineShape and buffer it by a certain amount such as the following



newAreaBaseShape = myLineShape.Buffer(200, 8, BufferCapType.Round, ThinkGeo.MapSuite.Core.GeographyUnit.DecimalDegree, ThinkGeo.MapSuite.Core.DistanceUnit.Feet)



I’d expect to get a buffer shape that is 400 feet wide around that line (200 feet on each side of the line).  And that’s exactly what I get on vertical lines, but on horizontal lines I’m getting something much wider, as if the math being performed behind the scenes is treating a degree of longitude as being equal to a degree in latitude as opposed to being considerably different in size.  I think I can work around this by converting my shape to another projection, such as UTM, performing the buffer, and then convert the result back to geographic, but it seems like if telling Map Suite to create a 200 foot buffer around a shape whose coordinates are in decimal degrees, it should actually create a 200 foot buffer as opposed to a buffer of a fraction of a decimal degree in width.



I’ve attached a picture of the results of buffering by 200 ft the line representing the outer ring of a rectangular polygon located approximately 48 degrees North Latitude.  The vertical edge was actually buffered by that 200 feet, but the horizontal edges were buffered by almost 300.





Hi John, 
  
 So what’s your projection you are using now for your scenario? I want to try to reproduce that and find what’s the reason. 
  
 And it looks your parameter in sample code use ThinkGeo.MapSuite.Core.GeographyUnit.DecimalDegree, I think if here you are in some other projection like 900913 you should want modify this parameter also. 
  
 Regards, 
  
 Don

The following is my projection string 
  
 “+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs” 
  
 The coordinates are latitude/longitude decimal degrees, so that parameter in my code is correct in saying that my geography units are decimal degrees. What I’m attempting to convey is that a buffer should be performed in the distance and distance units specified as it makes no sense to perform a buffer in decimal degrees as a degree in latitude does not equal a degree in longitude.  I’ve chosen to display the data in a conic conformal projection in the map above but that’s just for display, the data is stored (and the buffer is performed) in the projection I specify above. 
  
 I could reproject my shape to a different projection in which one unit in the north-south direction is approximately equal to one unit in the east-west direction and thereby work around the issue.  I just figured I should report the issue so that you know about it and hopefully fix it, but if you choose not to fix it at least others who run into the same issue I did will know there is an issue they need to work around. 
  
 Thanks, 
 John

Here’s a short snippet of sample code to demonstrate what I’m saying… 




class Program
{
static void Main(string[] args)
{
PointBaseShape pointOnEquator = new PointShape(0,0);
AreaBaseShape bufferPolygonOnEquator = pointOnEquator.Buffer(200, GeographyUnit.DecimalDegree, DistanceUnit.Meter);
double areaOnEquator = bufferPolygonOnEquator.GetArea(GeographyUnit.DecimalDegree, AreaUnit.SquareMeters);
Console.WriteLine("Area on equator = " + areaOnEquator);


PointBaseShape pointAt45North = new PointShape(0, 45);
AreaBaseShape bufferPolygonAt45North = pointAt45North.Buffer(200, GeographyUnit.DecimalDegree, DistanceUnit.Meter);
double areaAt45North = bufferPolygonAt45North.GetArea(GeographyUnit.DecimalDegree, AreaUnit.SquareMeters);
Console.WriteLine("Area at 45 North = " + areaAt45North);


PointBaseShape pointAt80North = new PointShape(0, 80);
AreaBaseShape bufferPolygonAt80North = pointAt80North.Buffer(200, GeographyUnit.DecimalDegree, DistanceUnit.Meter);
double areaAt80North = bufferPolygonAt80North.GetArea(GeographyUnit.DecimalDegree, AreaUnit.SquareMeters);
Console.WriteLine("Area at 80 North = " + areaAt80North);


Console.WriteLine("All Done ");
}
}





Creating a 200 meter buffer around a point should produce a circle of PI * (200 meters)^2 or approximately 125,663 square meters.  The Buffer function is not creating a buffer 200 meters in size, rather it appears to be taking 200 meters, converting that to decimal degrees, and then creating a buffer of that amount of decimal degrees around the point.  So, in effect creating an oval around the point as opposed to a circle around the point with the farther north one is the taller that oval becomes.  



Area on equator = 124857.795932134


Area at 45 North = 176575.588453293


Area at 80 North = 719027.260787964 
 



So we see that on the equator we get approximately a circle and approximately the area we would expect.  The farther we move north the more pronounced the oval and consequently the larger the area.

Hi John, 
  
 Thanks for your share. 
  
 Our Buffer should works like this: build a standard round based on the center point, so if you want to buffer a shape, you should want to convert the base shape to the rendering projection before buffer. 
  
 As below is my test code, which render a standard round in projection 900913, I think that should also works for your projection: 
  
  
  
   ManagedProj4Projection proj4 = new ManagedProj4Projection();
            proj4.InternalProjectionParametersString = ManagedProj4Projection.GetWgs84ParametersString();
            proj4.ExternalProjectionParametersString = ManagedProj4Projection.GetGoogleMapParametersString();
            proj4.Open();


            PointBaseShape p = new PointShape(0, 80);

            p = proj4.ConvertToExternalProjection§ as PointBaseShape;

            MultipolygonShape poly = p.Buffer(200, GeographyUnit.Meter, DistanceUnit.Meter);

            InMemoryFeatureLayer layer = new InMemoryFeatureLayer();
            layer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.Country1;
            layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            layer.InternalFeatures.Add(new Feature(poly));
            layer.Open();

            LayerOverlay overlay = new LayerOverlay();
            overlay.Layers.Add(layer);
            winformsMap1.Overlays.Add(overlay);

            winformsMap1.CurrentExtent = layer.GetBoundingBox();
            winformsMap1.MapUnit = GeographyUnit.Meter;
            winformsMap1.Refresh();
 
  
 Regards, 
  
 Don

Thank you for your reply.  I understand what you are saying and will be converting the shape to a different projection before performing the buffer in order to achieve the results I desire.  I’ll make one last attempt to convey why I think the existing behavior is not desirable and then I’ll shut up on the subject.



I’ll provide two arguments, one a user expectations argument and one a technical one.



User Expectations:

I’m not saying your code is not working as designed.  I’m suggesting that your design is not as intuitive as it should be.  Obviously it’s your software and you can design it to function however you want, but I’m just going to offer my opinion on how I think a user would expect it to function.  As an experiment, go to one or more of the less-technical people on your team and tell them you have a Longitude, Latitude point somewhere in the world.  You want to create a 100 meter buffer around that point.  Depending on how non-technical they are you might have to explain what a buffer is…  Then ask them how far west of that point that 100 meter buffer should extend.  If they say 100 meters, then so far so good.  Now ask them how far north of that point the 100 meter buffer should extend, if they say “it depends on how far you are from the equator” then I think you’ve got an intuitive design.  If instead they say “100 meters” then I’d suggest the existing behavior is not doing what users would expect it to do and ideally the design should be changed.



Technical:

To create a 100 meter buffer around a longitude, latitude coordinate at 45 North latitude in decimal degrees it appears to do these two steps

1. Convert 100 meters to approximately .00127 degrees

2. Buffer the point by .00127 degrees



I’d argue that step #1 is not appropriate as while 100 meters may be approximately .00127 degrees of longitude at 45 north, it is NOT .00127  degrees of latitude; it is approximately .0009 degrees of latitude.  Choosing any one value (in decimal degrees) to use to represent 100 meters is just flat out inappropriate to do.  In reality, at 45 north latitude, the value is every number between .0009 and .00127 (approximately) depending on which direction the buffer is to extend.  It cannot be simplified down to a single value and picking any one value between .0009 and .00127, while doing so is certainly expedient, will not result in creating a 100 meter buffer except for in the particular directions that match the value chosen.  I would argue that if the algorithm you are using requires you to choose a single value for 100 meters expressed in degrees, that it was an error to use that algorithm; instead it should have been abandoned for something which doesn’t rely on an invalid assumption.

Hi John, 
  
 Thanks very much for discuss about our API with us, that’s very helpful for make our product get better. 
  
 I am really sorry I hadn’t reply you so detail and maybe I hadn’t think this problem more detail, and because I am also not the API designer so maybe my thinking is not without error. That’s a good discussion here, I will let our development team knows it and they maybe can get more from our talking. 
  
 Here is my detail thinking about this problem: 
  
 1. Because our map is a control and most of our user is developer, so I think our API designer thought like a coder and default think our user have basic GIS tech. Here I have a question about your fist image, sorry I hadn’t ask about that before, it looks you mark the right side about 200ft and the bottom side about 298ft, and the projection should be “+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs”, I want to know whether the 200ft and 298ft is screen distance or really distance. This just like GetDistance, I think for two points in GIS, if we need calculate a distance between two points, we need consider the projection information but cannot directly calculate the screen distance, just because for same two points, their distance looks different under different projection. So that’s why if you want to get a standard buffered area, you need reprojection the shape, orelse it calculate the buffer based on 4326 and it won’t looks correct just as you want. 
  
 2. For your technical item, because we implement that by NTS library, so we cannot convert the distance to lonlat before pass that into the library. About this I think our developer can do further research. 
  
 Any question about our product please feel free to let us know. Thanks for your detail post again. 
  
 Regards, 
  
 Don

The projection of the Shapefile - how the data is stored - is “+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs” so that we can store all the data for the United States in a single table (in SQL Server).  We only work with the small subset for a single one of our insureds at a time, which we extract from SQL Server, and then write temporarily as Shapefile in our client application.  The projection of the display to the user is a conic conformal projection with a Central Meridian reasonably close to the location being viewed.  I think there may be some disconnect here due to the idea that this data needs projected.  I would argue that we display the data to the user projected to conic conformal so it looks “right” to them (north is directly up and distances in x and y are visually equal), but the projection in which the data is displayed to the user is essentially irrelevant to the data itself.  The data itself is a location on a somewhat-spherical-shaped planet.  If I say “give me a 100 meter buffer” around a point, regardless of the projection in which that point is stored, I should get a 100 meter buffer, not a buffer that is 100 meters in some directions and a different distance in others.   



Take any popular GIS for instance. 



From my use of MapInfo back in the day all buffering I did was in meters or feet to data stored in geographic NAD83 decimal degrees just like this and it would create a buffer of the distance (in meters or feet) that I specified, not attempt the invalid conversion to decimal degrees and then buffer by decimal degrees. 



From the ESRI online help one can see that they too, when asked to buffer data in a geographic coordinate system such as the NAD83 I’m working with here and specifying a buffer distance in linear units (such as meters or feet) create a buffer of the linear units requested, not something else. Link to the ESRI help page:  resources.arcgis.com/en/help/main/10.2/index.html#//000800000019000000 



QGIS takes a different tact in that they don’t allow you to specify units, instead all buffering is done in the units in which the data is stored thereby forcing you to reproject your data to a suitable projection before attempting a buffer as opposed to allowing you to enter values that it can’t correctly handle. 



I can see how relying on a 3rd party library somewhat ties your hands.  Although it would seem that if handed something you can’t handle, such as coordinate units of decimal degrees and distance units of meters, one could either return an error stating that it isn’t supported or better yet perform a conversion of the shape behind-the-scenes to a reasonable projection, compute the buffer by calling the 3rd party library passing the projected shape, reverse-project the resulting buffer shape and hand back the results.

Hi John, 
  
 Thanks for your detail reply, this is a very good discussion for us. I had sent this subject to our development team and I think it should be helpful for enhancement our APIs. 
  
 But I am sorry maybe we cannot modify currently logic for now, but I think if project need we can implement that by override the BufferCore function in BaseShape class as a workaround, which can make Buffer works more suitable your scenario. 
  
 Any suggestion or question please feel free to let us know. 
  
 Regards, 
  
 Don 


Thanks for bearing with me through my attempt to explain how ThinkGeo’s buffer behavior differs from expected.  I certainly don’t expect ThinkGeo to implement any short-term change to behavior, merely wished to raise the issue so you are aware of it and therefore have the possibility of considering it at some point in the possibly distant future. 
  
 The workaround of converting the shape to a different projection before calling buffer will work great for me.  It’s a few extra lines of code to do the conversion to and from a different projection, but I’ll bury all that (convert to a different projection, call buffer, convert result back to lat/long) in a function so it will merely involve making a one-line change each time I call buffer now to instead call my own buffer function instead. 
  
 Thanks, 
 John

Hi John, 
  
 Thanks for your understanding, we are glad to get feedback from our users, that’s really helpful for us. 
  
 Regards, 
  
 Don