1

I am using Python 3.6 with the latest version of shapely from Anaconda.

from shapely.geometry import Point,LineString
g = LineString(coordinates=[(0, 0), (6.656423206909781, 4.437570291332059)])
p  =wkt.loads('POINT (4.160264504318614 2.773481432082537)')
g.contains(p)
>>> False

The point p is clearly within geometry g, Since:

g.interpolate(5).xy
>>> (array('d', [4.160264504318614]), array('d', [2.773481432082537]))

Why is the above statement evaluating to false when I think it should be true?

11
  • 1
    @BERA g contains its half-way point: g.contains(g.interpolate(0.5, True)) - is True, so lines can contain points. Note it doesn't contain its 0.25-way point.
    – Spacedman
    Commented Sep 29, 2018 at 8:36
  • Why doesn't it contain its 0.25 way point?
    – user32882
    Commented Sep 29, 2018 at 8:38
  • shapely thinks it doesn't contain its 0.25-way point.
    – Spacedman
    Commented Sep 29, 2018 at 8:38
  • 2
    This is more rant than question. Please tone down the stridency in your question to make it more appropriate for professional settings.
    – Vince
    Commented Sep 29, 2018 at 14:49
  • 3
    I'm voting to close this question as off-topic because duplicate of stackoverflow.com/questions/21291725/…
    – Spacedman
    Commented Sep 29, 2018 at 16:14

2 Answers 2

6

Simply look at the answer of Mike T in Determine if shapely point is within a linestring/multilinestring

There are floating point precision errors when finding a point on a line. Use the distance with an appropriate threshold instead.

 g.distance(p) < 1e-8  # True
 True
8
  • I would retort by the same argument as @ds_user... namely how can we automate the selection of a suitable threshhold? This is part of a generic program for which the input data will be varied, and inevitably require different threshholds. For some data a threshhold may be too large and for other data it may be too small. Please also see this question which is why I need to be able to do this: gis.stackexchange.com/questions/297406/…
    – user32882
    Commented Sep 29, 2018 at 9:16
  • see Shapely intersections vs shapely relationships - inexact?
    – gene
    Commented Sep 29, 2018 at 9:47
  • how to determine the machine epsilon? Is it consistent throughout (independent of geometry type, machine)? in that case it might make sense to use a threshhold....
    – user32882
    Commented Sep 29, 2018 at 9:51
  • The JTS Topology Suite give me the same result (false) so it's not just a shapely problem.
    – gene
    Commented Sep 29, 2018 at 9:59
  • In this example can I simply replace 1e-8 as you have in your boolean statement by something like print(numpy.finfo(float).eps)? How can I figure out the exact precision used by shapely, which should work for all line/point data regardless of its projection (CRS), extent or size
    – user32882
    Commented Sep 29, 2018 at 10:07
2

Taking your original example:

from shapely.geometry import Point,LineString
g = LineString(coordinates=[(0, 0), (6.656423206909781, 4.437570291332059)])
p  =wkt.loads('POINT (4.160264504318614 2.773481432082537)')
g.contains(p)

my first thought was that the string representation of a floating point number can be something that can not be represented exactly by a computer as a floating-point number in 8 bytes. For a trivial example, if I type 1.2222 and keep typing 2s, eventually python can't keep that precision and shows the last digit as a 3:

>>> 1.22222222222222222
1.2222222222222223

[ There's a nice intro to floating point here: https://docs.python.org/3/tutorial/floatingpoint.html ]

So a better test is to take the number from Python's calculation itself rather than typing them into a text string in a WKT. So I tried:

>>> g.interpolate(0.5, True).xy
(array('d', [3.3282116034548905]), array('d', [2.2187851456660295]))

Now that return value is stored in Python in whatever precision it is using. That point looks reasonably as half-way along the line, so lets try it:

>>> g.contains(g.interpolate(0.5, True))
True

Okay, that seems fine. But maybe halfway is somehow special because computers work with binary numbers and division by two is easy in binary? Anyway, always a good idea to try some more tests. Lets' try another point, this time a little bit past halfway:

>>> g.contains(g.interpolate(0.50001, True))
False

Now that perhaps is a little surprising. This shows directly that a point interpolated on a line is no longer thought to be contained by that line.

I wondered if numeric precision was having this effect. So I tried computing the distance from these interpolated points to the line:

>>> g.distance(g.interpolate(0.5, True))
0.0
>>> g.distance(g.interpolate(0.50001, True))
0.0

And these zeroes are identical zeroes.

I suspect floating point arithmetic rounding errors in the contains method are occurring here. If you want to detect if a point is on a line then you should perhaps use the distance method and then test the result is less than some small threshold to account for any precision problems.

1
  • II prefer not to go for the distance method, this is to be part of a generic program which should be able to take large vector files of any shape and size and for which a threshhold is not applicable. These points are known to be within the lines from the get-go. The distance method is more for points which are slightly or drastically offset from a line, not for points which we know are on the line...Here's another question I wrote which is somewhat related to the above gis.stackexchange.com/questions/297406/…
    – user32882
    Commented Sep 29, 2018 at 9:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.