Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Closed
wants to merge 1 commit into from

3 participants

@claudep
Collaborator

No description provided.

@akaariai
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 :)

@jbronn
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.

@jbronn 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))
@jbronn 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
@jbronn
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.

@jbronn
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.

@claudep
Collaborator

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

@claudep
Collaborator

Pushed in 2f6e00a

@claudep 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.
View
24 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))
@jbronn 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)
View
23 django/contrib/gis/geos/prototypes/topology.py
@@ -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'])
View
21 django/contrib/gis/geos/tests/test_geos.py
@@ -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
View
25 docs/ref/contrib/gis/geos.txt
@@ -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
View
6 docs/releases/1.5.txt
@@ -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.