Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why does a buffered point result in a oval? #110

Closed
djdmbrwsk opened this issue May 2, 2014 · 36 comments
Closed

Why does a buffered point result in a oval? #110

djdmbrwsk opened this issue May 2, 2014 · 36 comments

Comments

@djdmbrwsk
Copy link
Contributor

I know there is probably some geographical/mathematical reason, but why does a buffered point result in a oval polygon? Thanks!

@djdmbrwsk
Copy link
Contributor Author

Oh duh. So if your Point has coordinates: [0, 0] you get a perfect circle. The reason i'm getting an oval is because I'm in the Northern hemisphere. Great library, thanks for all your hard work!

@morganherlocker
Copy link
Member

Thanks! You guessed it though, the issue is that web maps typically use projections that distort shapes the closer they get to the poles. Let me know if you run into any other issues or have any feature requests; feedback is always very helpful.

@djdmbrwsk djdmbrwsk reopened this May 18, 2014
@djdmbrwsk
Copy link
Contributor Author

Well I thought I understood this, but I guess not.

When I create a polygon at [0, 0] using turf by buffering it, it is perfectly circular. That makes sense because when projecting a spherical map to a flat surface there is less distortion at the equator. If I create the same polygon at a Boston lon/lat the result is an oval (even if it's just a 5 mile buffer). Because of map projection scaling shouldn't I really only see distortion on very large polygons? And when I do, assuming they're circular, shouldn't they look like an upside-down egg?

@morganherlocker
Copy link
Member

I will need to explore this some more, but I think that the issue comes from using 4326 lat,lon data and projecting it over to 3857 (what most web maps use under the hood). One thing to try would be transforming your data over before buffering using something like proj4js. It is my understanding that the distortion in 3857 is actually pretty significant, and that you would see this sort of thing even at 5 miles.

@djdmbrwsk
Copy link
Contributor Author

This has opened up a can of worms for me. I've been doing a bunch of reading and these different projection standards are quite confusing. But from what I've gathered so far though 4326 is like the standard, and as such most web map APIs take 4326 as input and translate it to whatever they need to display it (mainly 3857). Does that sound right?

If that's the case I wouldn't think I would have to do anything, but there is definitely something funky going on here.

@atdrago
Copy link
Contributor

atdrago commented May 19, 2014

@morganherlocker I admittedly don't know as much about this stuff as you do, however I was looking at JTS (the parent of JSTS), and it seems like it is built for a 2d, linear plane. From the JTS site:

JTS provides a complete, consistent, robust implementation of fundamental algorithms for processing linear geometry on the 2-dimensional Cartesian plane

I created a little test that takes in @djdmbrwsk's GeoJson file and buffers the reference point (instead of using the feature collection he provided), and then computes the distance to each vertex of the buffered polygon. The points created should seemingly be 3 or close to it, however they fluctuate between 3 -> 2.2 -> 3 -> 2.2 -> 3, which is the oval shape he is talking about.

@atdrago
Copy link
Contributor

atdrago commented May 20, 2014

I believe I have the buffering of a Point working now, using LatLon.prototype.destinationPoint from this blog to move in a circular direction around the point at a given distance, at a fixed number of points along the circumference of the circle to create a circular shaped polygon:

https://gist.github.com/atdrago/08a42bac9f887243b8d5

I'll try to get Polygon buffering working tomorrow using similar concepts.

@morganherlocker
Copy link
Member

This is terrific, and that post is very helpful. Polygon buffers get pretty tricky when you get into complex polygons, but full geodesic buffers would be a huge win. I think you are definitely on the right track.

@djdmbrwsk
Copy link
Contributor Author

@atdrago Thanks a ton for running some tests to help get to the bottom of this!

@morganherlocker Before we get too deep, you are looking for turf to be a truly geodesic library correct?

@morganherlocker
Copy link
Member

@djdmbrwsk Yes. We may want to support planar for some reason in the future, but I think geodesic makes the most sense, and is definitely what people are expecting. Turf is specifically for geospatial data, not geometric data.

@djdmbrwsk
Copy link
Contributor Author

I saw there was a bunch of organizational/clean up activity today, nice! We should probably mark this issue as a bug...it's a pretty nasty one. I know @atdrago has point buffering working geodesically and he's working on polygons, but I don't know how far he has gotten.

@atdrago
Copy link
Contributor

atdrago commented Jun 5, 2014

Sorry for the delay. I went on vacation for about 11 days there.

The Point buffering was very easy, and I made attempts to get Line buffering to work, but they were not successful. Simply getting two lines that are parallel to the initial line at a specified distance turned out to be non-trivial. The road block I hit was trying to get the bearing perpendicular to the initial line's bearing so that I could measure the buffered distance away from the line (at that bearing), and draw a line at that distance with the same bearing as the initial line.

At this point, I'm going to give up on it, as it isn't terribly important to the work I need to do elsewhere, and in the meantime that work has been suffering. I do plan to loop back to this eventually if somebody else hasn't picked it up by the time I'm finished with the project I'm working on. I'm glad to see you plan to remove JSTS from turf, as it really seems bloated and inefficient (not to mention the fact that it is meant for 2d spaces). Good luck... I hope to contribute again soon!

@morganherlocker
Copy link
Member

@atdrago No worries. You are not the first, and certainly not the last to attempt solving the issue (including myself). I think we will probably support geodesic points in the short term based on your code, then make a crack at lines and polygons later on.

PostGIS and a few others have solved the problem, but it is clearly non-trivial, even on a 2d plane. Arcgis only started supporting geodesic buffers on polys a couple years ago and JTS has been used for years without it, so I do not feel too far behind. Thanks for your hard work! 👍

@antoniolocandro
Copy link

Hi I don't know if this is the right place to post this but in this document http://www.faa.gov/documentLibrary/media/Order/8260.54A.pdf in Appendix 2 there are lots of calculations and algorithms for solving some useful problems when working with non projected data e.g. EPSG 4326

There is also this github repo https://github.com/pkohut/GeoFormulas and http://wired2code.wordpress.com/2010/07/14/wgs84-ellipsoid-calculations/

For easy references cases covered are here http://www.pkautomation.com/wgs84_geodesic_calc.html
Hope this is useful in any way

@rowanwins
Copy link
Member

Probably on a similar issue... Running a buffer on a point seems to return a very jagged circle for me at the moment. I've tried on a small buffer (eg 1km) as well as a larger one (20km) and they both seem to generate similar jaggedness. I would've expected a smoother polygon? Any tips?
bufferexample

On a more positive note its been really fun playing with turf, it's remarkably simple to use so thank you!
Rowan

@morganherlocker
Copy link
Member

thanks @rowanwins! Buffers are circles... but geojson only really represents polygons as lists of edges. This means that a "perfect" circle is not actually possible. This is a problem related to the coastline paradox. In a nutshell, a circle has an infinite number of vertices if defined as a polygon, so you have to decide on a resolution for your vertices. This is currently fixed in turf-buffer, and it is unaffected by the size of the buffer.

Also note that Leaflet does some automatic simplification of its own, so the display may be more jagged when zoomed out, even if the data you are using for calculations is more precise.

There is a branch of turf-buffer I will be pushing out in a couple days that will give you perfectly circular point buffers, as well as an optional curve resolution. This means you can specify whatever detail you would like (memory and performance will be affected, but you can tinker with it).

@rowanwins
Copy link
Member

thanks @morganherlocker for the description, will await the update!

@manelclos
Copy link

Got into this issue today. Easy to catch if you compare it to turf.destination:

    var point = map.getCenter();
    var pt1 = turf.point([point.lng, point.lat]);

    if (geoms) {
        map.removeLayer(geoms);
    }

    var minx = turf.destination(pt1, 0.200, 270, 'kilometers');
    var miny = turf.destination(pt1, 0.200, 180, 'kilometers');
    var maxx = turf.destination(pt1, 0.200, 90, 'kilometers');
    var maxy = turf.destination(pt1, 0.200, 0, 'kilometers');
    var env1 = turf.envelope(turf.featurecollection([minx, miny, maxx, maxy]));

    var buffered = turf.buffer(pt1, 0.200, 'kilometers');
    var env2 = turf.envelope(buffered);
    geoms = L.geoJson([buffered, env2, env1, pt1, minx, miny, maxx, maxy]).addTo(map);

@djdmbrwsk
Copy link
Contributor Author

FYI, @morganherlocker did make some awesome progress on the issue. In this turf-buffer branch geodesic Point, LineString, and MultiLineString buffering is working.

@manelclos
Copy link

Tried the pointBuffer function and it works perfect, same result! Thanks!

@floledermann
Copy link

Letting the user specify real-world units for the current buffer implementation is definitely a bug, as these "units" are merely statically "converted" to degrees and the buffer is then calculated using degrees - which, depending on latitude, can give vastly diverging (and wrong!) results in the current implementation, like the ellipses observed by many for the point-buffering case. As far as my understanding goes, the current Turf.js implementation does not calculate a "geodesic buffer" (which would calculate the correct shape on the ellipsoid/geoid), nor an "euclidean buffer", which would have to be calculated in the projection plane (and therefore take the map projection as an argument), but is a naïve implementation treating degrees as cartesian coordinates (so technically I guess it could be described as euclidean buffering using a fixed identity/equirectangular projection). Which is fine, if only it were properly documented and not taking a meaningless and misleading "unit" parameter that suggests geodesic buffering. (QGis does the same thing in their buffer function, only it (correctly) doesn't let you specify a "unit")

This is very unfortunate as creating a buffer from a point is the first thing in practically all the Turf.js tutorials, so you start out with something that is wrong and confusing. :(

@morganherlocker
Copy link
Member

Closing in favor of the issue on turf-buffer. TLDR: geodesic buffers are in progress. I expect them to be in the next major turf release, but geodesic polygon buffers are complex, so your patience is appreciated.

@bensleveritt
Copy link

Hate to dredge up old issues, but has the geodesic buffer for points been merged in? I'm seeing an oval, and I wanted to check it wasn't just me.

The issue on turf-buffer doesn't go anywhere, has that project been merged into this one since then?

@Antenka
Copy link

Antenka commented Jun 1, 2016

Hello, I can see the updates in the standalone project here: https://github.com/Turfjs/turf-buffer/blob/artisinal/index.js
But the general packages seems like not updated:
https://github.com/Turfjs/turf/blob/master/packages/turf-buffer/index.js

Also as the client-side libruary: turf.min.js

Any plans about spreading the change around?

@tmcw
Copy link
Collaborator

tmcw commented Jun 1, 2016

Hi Antenka,

The file you link to is in the 'artisinal' branch of turf-buffer: the 'master' branch is the one containing finished, deployed work. The other branch is not yet finished - when it is finished, it will be merged into master and the buffers generated by master will follow its approach.

@Antenka
Copy link

Antenka commented Jun 1, 2016

Hi, thanks for such fast response. Any plans about when it planned to be finished/merged?

@tmcw
Copy link
Collaborator

tmcw commented Jun 1, 2016

Unfortunately not; you can refer to the JSTS removal ticket - #88 for the depth of this task. Many people have tried, none have succeeded in finishing the fundamental geometry algorithms we need to move past JSTS. Your help would be much appreciated if you can offer it.

@mclaeysb
Copy link

Hi there! I gave it a try. See my pull request in turf-buffer. All comments welcome!

@jaapster
Copy link

jaapster commented Nov 6, 2017

Hi, any progress on this issue? I too get the oval and if I measure the radii from the center of the oval, it is clear that this buffer is not calculated correctly. Different distance in NS direction compared to SW direction.

@marcosnc08
Copy link

Hi! I'm having the same issue. Could anyone find a solution?

@greetclock
Copy link

I faced the same problem and then I realised that I have turf package instead of @turf/turf.

@drio
Copy link

drio commented Sep 18, 2019

I am running into the same issue described by @djdmbrwsk when running the following code.

I am trying to create a buffered point and then compute programmatically if other locations are within the "area" described by the buffered point.
The issue is that the area created by the buffered point is not a circle but an oval.

Details and code here:

  function addBuffer(location, radius) {
    const stopPoint = turf.point(location);
    return turf.buffer(stopPoint, radius, {units: "miles"});
  }

  const ZOOM = 15;
  const map = L.map('mapid').setView(LOCATIONS.charleston, ZOOM);
  const radiusBufferMeters = 100;
  const LOCATIONS = {  
   charleston:                [32.7794759, -79.9340512],
   secondStateCoffee        : [32.779694, -79.937644],
   closeToSecondStateCoffee : [32.779663, -79.937027],
   colonialLake             : [32.778191, -79.941629],
  }

  L.tileLayer(MAP_URL, {
    attribution: ATTR,
    minZoom: ZOOM,
    maxZoom: ZOOM,
    boxZoom: false,
    zoomControl: false,
    dragging: false
  }).addTo(map);

  L.marker(LOCATIONS.secondStateCoffee, {opacity: 0.8}).addTo(map);
  L.marker(LOCATIONS.closeToSecondStateCoffee, {opacity: 0.6}).addTo(map);
  L.marker(LOCATIONS.colonialLake, {opacity: 0.8}).addTo(map);

  const circle = L.circle(LOCATIONS.secondStateCoffee, {radius: radiusBufferMeters});
  circle.addTo(map);

  // leafleat circle
  const tCircle = turf.circle(LOCATIONS.secondStateCoffee, radiusBufferMeters, {units: 'meters'});
  const gjLayer = L.geoJson(tCircle);
  L.polygon(tCircle.geometry.coordinates, {color: "red"}).addTo(map);
  
  // turf buffered Point
  const bufferedPoint = addBuffer(LOCATIONS.secondStateCoffee, radiusBufferMeters/1000);
  L.polygon(bufferedPoint.geometry.coordinates, {color: "green"}).addTo(map);

Output:

Screen Shot 2019-09-18 at 3 59 31 PM

I have also written a little observable notebook while exploring the issue. Visual aim here:

gis

@artuska
Copy link

artuska commented Nov 20, 2019

Faced the same issue — circle method produces ellipsis (oval) geometry. Why? #1793

@stebogit
Copy link
Collaborator

@drio you might want to take a look at this

@omar-alnayme
Copy link

omar-alnayme commented Sep 28, 2020

Hi @morganherlocker is the oval buffer problem solved, I would like to thank you for the fantastic library.

@MacaScull
Copy link

MacaScull commented May 12, 2021

Sorry to bring up old issues but did anyone manage to produce geodesic buffers for polygons? Also I was just wondering what algorithm you use to produce the geodesic buffers for all shapes (points, lines, polygons)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests