Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixed #11948 -- Added interpolate and project linear referencing methods #168

Closed
wants to merge 1 commit into from

3 participants

Claude Paroz Anssi Kääriäinen Justin Bronn
Claude Paroz
Collaborator

No description provided.

Anssi Kääriäinen
Collaborator

I don't know gis well enough to comment on the code of the patch.

But I have verified the patch as well as I can - that is, the gis test suite passes on postgis after applying this pull request, the docs build without warnings and the code looks to be good quality.

So, LGTM with the warning that I don't know what the code actually does :)

Justin Bronn
Collaborator

I'm going to review this; the PostGIS 2.0 requirements will probably need this, so it may come have to come in through a different PR.

Justin Bronn jbronn commented on the diff
django/contrib/gis/geos/geometry.py
@@ -577,6 +577,16 @@ def envelope(self):
"Return the envelope for this geometry (a polygon)."
return self._topology(capi.geos_envelope(self.ptr))
+ def interpolate(self, distance):
+ if not isinstance(self, (LineString, MultiLineString)):
+ raise TypeError('interpolate only works on LineString and MultiLineString geometries')
+ return self._topology(capi.geos_interpolate(self.ptr, distance))
Justin Bronn Collaborator
jbronn added a note

If there's GEOS 3.1 (as on Ubuntu 10.04), however you'll need to raise a NotImplementedError.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Justin Bronn
Collaborator

Did an initial review, and liked what I see so far. I need to check if there's a variable somewhere that makes it easier to check for GEOS revision in a way more granular than what was done for when prepared geometry support: a less than elegant GEOS_PREPARE global was used previously for version-based differences, if I recall correctly.

Justin Bronn
Collaborator

Yeah, there's already a GEOS_VERSION global; another approach would be to check for the existence of those functions on the capi module.

Claude Paroz
Collaborator

OK, I will use your suggestion of checking function availability on the capi module

Claude Paroz
Collaborator

Pushed in 2f6e00a

Claude Paroz claudep closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
24 django/contrib/gis/geos/geometry.py
View
@@ -577,6 +577,16 @@ def envelope(self):
"Return the envelope for this geometry (a polygon)."
return self._topology(capi.geos_envelope(self.ptr))
+ def interpolate(self, distance):
+ if not isinstance(self, (LineString, MultiLineString)):
+ raise TypeError('interpolate only works on LineString and MultiLineString geometries')
+ return self._topology(capi.geos_interpolate(self.ptr, distance))
Justin Bronn Collaborator
jbronn added a note

If there's GEOS 3.1 (as on Ubuntu 10.04), however you'll need to raise a NotImplementedError.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ def interpolate_normalized(self, distance):
+ if not isinstance(self, (LineString, MultiLineString)):
+ raise TypeError('interpolate only works on LineString and MultiLineString geometries')
+ return self._topology(capi.geos_interpolate_normalized(self.ptr, distance))
+
def intersection(self, other):
"Returns a Geometry representing the points shared by this Geometry and other."
return self._topology(capi.geos_intersection(self.ptr, other.ptr))
@@ -586,6 +596,20 @@ def point_on_surface(self):
"Computes an interior point of this Geometry."
return self._topology(capi.geos_pointonsurface(self.ptr))
+ def project(self, point):
+ if not isinstance(point, Point):
+ raise TypeError('locate_point argument must be a Point')
+ if not isinstance(self, (LineString, MultiLineString)):
+ raise TypeError('locate_point only works on LineString and MultiLineString geometries')
+ return capi.geos_project(self.ptr, point.ptr)
+
+ def project_normalized(self, point):
+ if not isinstance(point, Point):
+ raise TypeError('locate_point argument must be a Point')
+ if not isinstance(self, (LineString, MultiLineString)):
+ raise TypeError('locate_point only works on LineString and MultiLineString geometries')
+ return capi.geos_project_normalized(self.ptr, point.ptr)
+
def relate(self, other):
"Returns the DE-9IM intersection matrix for this Geometry and the other."
return capi.geos_relate(self.ptr, other.ptr)
23 django/contrib/gis/geos/prototypes/topology.py
View
@@ -8,18 +8,18 @@
'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate']
from ctypes import c_double, c_int
-from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
-from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
+from django.contrib.gis.geos.libgeos import geos_version_info, GEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_minus_one, check_string
from django.contrib.gis.geos.prototypes.geom import geos_char_p
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
-def topology(func, *args):
+def topology(func, *args, **kwargs):
"For GEOS unary topology functions."
argtypes = [GEOM_PTR]
if args: argtypes += args
func.argtypes = argtypes
- func.restype = GEOM_PTR
- func.errcheck = check_geom
+ func.restype = kwargs.get('restype', GEOM_PTR)
+ func.errcheck = kwargs.get('errcheck', check_geom)
return func
### Topology Routines ###
@@ -49,3 +49,16 @@ def topology(func, *args):
geos_cascaded_union.argtypes = [GEOM_PTR]
geos_cascaded_union.restype = GEOM_PTR
__all__.append('geos_cascaded_union')
+
+# Linear referencing routines
+info = geos_version_info()
+if info['version'] >= '3.2.0':
+ geos_project = topology(GEOSFunc('GEOSProject'), GEOM_PTR,
+ restype=c_double, errcheck=check_minus_one)
+ geos_interpolate = topology(GEOSFunc('GEOSInterpolate'), c_double)
+
+ geos_project_normalized = topology(GEOSFunc('GEOSProjectNormalized'),
+ GEOM_PTR, restype=c_double, errcheck=check_minus_one)
+ geos_interpolate_normalized = topology(GEOSFunc('GEOSInterpolateNormalized'), c_double)
+ __all__.extend(['geos_project', 'geos_interpolate',
+ 'geos_project_normalized', 'geos_interpolate_normalized'])
21 django/contrib/gis/geos/tests/test_geos.py
View
@@ -1017,6 +1017,27 @@ def test_valid_reason(self):
print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n")
+ @unittest.skipUnless(geos_version_info()['version'] >= '3.2.0', "geos >= 3.2.0 is required")
+ def test_linearref(self):
+ "Testing linear referencing"
+
+ ls = fromstr('LINESTRING(0 0, 0 10, 10 10, 10 0)')
+ mls = fromstr('MULTILINESTRING((0 0, 0 10), (10 0, 10 10))')
+
+ self.assertEqual(ls.project(Point(0, 20)), 10.0)
+ self.assertEqual(ls.project(Point(7, 6)), 24)
+ self.assertEqual(ls.project_normalized(Point(0, 20)), 1.0/3)
+
+ self.assertEqual(ls.interpolate(10), Point(0, 10))
+ self.assertEqual(ls.interpolate(24), Point(10, 6))
+ self.assertEqual(ls.interpolate_normalized(1.0/3), Point(0, 10))
+
+ self.assertEqual(mls.project(Point(0, 20)), 10)
+ self.assertEqual(mls.project(Point(7, 6)), 16)
+
+ self.assertEqual(mls.interpolate(9), Point(0, 9))
+ self.assertEqual(mls.interpolate(17), Point(10, 7))
+
def test_geos_version(self):
"Testing the GEOS version regular expression."
from django.contrib.gis.geos.libgeos import version_regex
25 docs/ref/contrib/gis/geos.txt
View
@@ -402,11 +402,36 @@ quarter circle (defaults is 8).
Returns a :class:`GEOSGeometry` representing the points making up this
geometry that do not make up other.
+.. method:: GEOSGeometry.interpolate(distance)
+.. method:: GEOSGeometry.interpolate_normalized(distance)
+
+.. versionadded:: 1.5
+
+Given a distance (float), returns the point (or closest point) within the
+geometry (:class:`LineString` or :class:`MultiLineString`) at that distance.
+The normalized version takes the distance as a float between 0 (origin) and 1
+(endpoint).
+
+Reverse of :meth:`GEOSGeometry.project`.
+
.. method:: GEOSGeometry:intersection(other)
Returns a :class:`GEOSGeometry` representing the points shared by this
geometry and other.
+.. method:: GEOSGeometry.project(point)
+.. method:: GEOSGeometry.project_normalized(point)
+
+.. versionadded:: 1.5
+
+Returns the distance (float) from the origin of the geometry
+(:class:`LineString` or :class:`MultiLineString`) to the point projected on the
+geometry (that is to a point of the line the closest to the given point).
+The normalized version returns the distance as a float between 0 (origin) and 1
+(endpoint).
+
+Reverse of :meth:`GEOSGeometry.interpolate`.
+
.. method:: GEOSGeometry.relate(other)
Returns the DE-9IM intersection matrix (a string) representing the
6 docs/releases/1.5.txt
View
@@ -102,6 +102,12 @@ Django 1.5 also includes several smaller improvements worth noting:
* In the localflavor for Canada, "pq" was added to the acceptable codes for
Quebec. It's an old abbreviation.
+* :class:`~django.contrib.gis.geos.LineString` and
+ :class:`~django.contrib.gis.geos.MultiLineString` GEOS objects now support the
+ :meth:`~django.contrib.gis.geos.GEOSGeometry.interpolate()` and
+ :meth:`~django.contrib.gis.geos.GEOSGeometry.project()` methods
+ (so-called linear referencing).
+
Backwards incompatible changes in 1.5
=====================================
Something went wrong with that request. Please try again.