Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

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
Owner

No description provided.

Anssi Kääriäinen
Owner

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
Owner

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 September 09, 2012
django/contrib/gis/geos/geometry.py
@@ -577,6 +577,16 @@ def envelope(self):
577 577
         "Return the envelope for this geometry (a polygon)."
578 578
         return self._topology(capi.geos_envelope(self.ptr))
579 579
 
  580
+    def interpolate(self, distance):
  581
+        if not isinstance(self, (LineString, MultiLineString)):
  582
+            raise TypeError('interpolate only works on LineString and MultiLineString geometries')
  583
+        return self._topology(capi.geos_interpolate(self.ptr, distance))
1
Justin Bronn Owner
jbronn added a note September 09, 2012

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
Owner

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
Owner

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
Owner

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

Claude Paroz
Owner

Pushed in 2f6e00a

Claude Paroz claudep closed this September 29, 2012
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
@@ -577,6 +577,16 @@ def envelope(self):
577 577
         "Return the envelope for this geometry (a polygon)."
578 578
         return self._topology(capi.geos_envelope(self.ptr))
579 579
 
  580
+    def interpolate(self, distance):
  581
+        if not isinstance(self, (LineString, MultiLineString)):
  582
+            raise TypeError('interpolate only works on LineString and MultiLineString geometries')
  583
+        return self._topology(capi.geos_interpolate(self.ptr, distance))
  584
+
  585
+    def interpolate_normalized(self, distance):
  586
+        if not isinstance(self, (LineString, MultiLineString)):
  587
+            raise TypeError('interpolate only works on LineString and MultiLineString geometries')
  588
+        return self._topology(capi.geos_interpolate_normalized(self.ptr, distance))
  589
+
580 590
     def intersection(self, other):
581 591
         "Returns a Geometry representing the points shared by this Geometry and other."
582 592
         return self._topology(capi.geos_intersection(self.ptr, other.ptr))
@@ -586,6 +596,20 @@ def point_on_surface(self):
586 596
         "Computes an interior point of this Geometry."
587 597
         return self._topology(capi.geos_pointonsurface(self.ptr))
588 598
 
  599
+    def project(self, point):
  600
+        if not isinstance(point, Point):
  601
+            raise TypeError('locate_point argument must be a Point')
  602
+        if not isinstance(self, (LineString, MultiLineString)):
  603
+            raise TypeError('locate_point only works on LineString and MultiLineString geometries')
  604
+        return capi.geos_project(self.ptr, point.ptr)
  605
+
  606
+    def project_normalized(self, point):
  607
+        if not isinstance(point, Point):
  608
+            raise TypeError('locate_point argument must be a Point')
  609
+        if not isinstance(self, (LineString, MultiLineString)):
  610
+            raise TypeError('locate_point only works on LineString and MultiLineString geometries')
  611
+        return capi.geos_project_normalized(self.ptr, point.ptr)
  612
+
589 613
     def relate(self, other):
590 614
         "Returns the DE-9IM intersection matrix for this Geometry and the other."
591 615
         return capi.geos_relate(self.ptr, other.ptr)
23  django/contrib/gis/geos/prototypes/topology.py
@@ -8,18 +8,18 @@
8 8
            'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate']
9 9
 
10 10
 from ctypes import c_double, c_int
11  
-from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
12  
-from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
  11
+from django.contrib.gis.geos.libgeos import geos_version_info, GEOM_PTR, GEOS_PREPARE
  12
+from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_minus_one, check_string
13 13
 from django.contrib.gis.geos.prototypes.geom import geos_char_p
14 14
 from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
15 15
 
16  
-def topology(func, *args):
  16
+def topology(func, *args, **kwargs):
17 17
     "For GEOS unary topology functions."
18 18
     argtypes = [GEOM_PTR]
19 19
     if args: argtypes += args
20 20
     func.argtypes = argtypes
21  
-    func.restype = GEOM_PTR
22  
-    func.errcheck = check_geom
  21
+    func.restype = kwargs.get('restype', GEOM_PTR)
  22
+    func.errcheck = kwargs.get('errcheck', check_geom)
23 23
     return func
24 24
 
25 25
 ### Topology Routines ###
@@ -49,3 +49,16 @@ def topology(func, *args):
49 49
     geos_cascaded_union.argtypes = [GEOM_PTR]
50 50
     geos_cascaded_union.restype = GEOM_PTR
51 51
     __all__.append('geos_cascaded_union')
  52
+
  53
+# Linear referencing routines
  54
+info = geos_version_info()
  55
+if info['version'] >= '3.2.0':
  56
+    geos_project = topology(GEOSFunc('GEOSProject'), GEOM_PTR,
  57
+        restype=c_double, errcheck=check_minus_one)
  58
+    geos_interpolate = topology(GEOSFunc('GEOSInterpolate'), c_double)
  59
+
  60
+    geos_project_normalized = topology(GEOSFunc('GEOSProjectNormalized'),
  61
+        GEOM_PTR, restype=c_double, errcheck=check_minus_one)
  62
+    geos_interpolate_normalized = topology(GEOSFunc('GEOSInterpolateNormalized'), c_double)
  63
+    __all__.extend(['geos_project', 'geos_interpolate',
  64
+        'geos_project_normalized', 'geos_interpolate_normalized'])
21  django/contrib/gis/geos/tests/test_geos.py
@@ -1017,6 +1017,27 @@ def test_valid_reason(self):
1017 1017
 
1018 1018
         print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n")
1019 1019
 
  1020
+    @unittest.skipUnless(geos_version_info()['version'] >= '3.2.0', "geos >= 3.2.0 is required")
  1021
+    def test_linearref(self):
  1022
+        "Testing linear referencing"
  1023
+
  1024
+        ls = fromstr('LINESTRING(0 0, 0 10, 10 10, 10 0)')
  1025
+        mls = fromstr('MULTILINESTRING((0 0, 0 10), (10 0, 10 10))')
  1026
+
  1027
+        self.assertEqual(ls.project(Point(0, 20)), 10.0)
  1028
+        self.assertEqual(ls.project(Point(7, 6)), 24)
  1029
+        self.assertEqual(ls.project_normalized(Point(0, 20)), 1.0/3)
  1030
+
  1031
+        self.assertEqual(ls.interpolate(10), Point(0, 10))
  1032
+        self.assertEqual(ls.interpolate(24), Point(10, 6))
  1033
+        self.assertEqual(ls.interpolate_normalized(1.0/3), Point(0, 10))
  1034
+
  1035
+        self.assertEqual(mls.project(Point(0, 20)), 10)
  1036
+        self.assertEqual(mls.project(Point(7, 6)), 16)
  1037
+
  1038
+        self.assertEqual(mls.interpolate(9), Point(0, 9))
  1039
+        self.assertEqual(mls.interpolate(17), Point(10, 7))
  1040
+
1020 1041
     def test_geos_version(self):
1021 1042
         "Testing the GEOS version regular expression."
1022 1043
         from django.contrib.gis.geos.libgeos import version_regex
25  docs/ref/contrib/gis/geos.txt
@@ -402,11 +402,36 @@ quarter circle (defaults is 8).
402 402
 Returns a :class:`GEOSGeometry` representing the points making up this
403 403
 geometry that do not make up other.
404 404
 
  405
+.. method:: GEOSGeometry.interpolate(distance)
  406
+.. method:: GEOSGeometry.interpolate_normalized(distance)
  407
+
  408
+.. versionadded:: 1.5
  409
+
  410
+Given a distance (float), returns the point (or closest point) within the
  411
+geometry (:class:`LineString` or :class:`MultiLineString`) at that distance.
  412
+The normalized version takes the distance as a float between 0 (origin) and 1
  413
+(endpoint).
  414
+
  415
+Reverse of :meth:`GEOSGeometry.project`.
  416
+
405 417
 .. method:: GEOSGeometry:intersection(other)
406 418
 
407 419
 Returns a :class:`GEOSGeometry` representing the points shared by this
408 420
 geometry and other.
409 421
 
  422
+.. method:: GEOSGeometry.project(point)
  423
+.. method:: GEOSGeometry.project_normalized(point)
  424
+
  425
+.. versionadded:: 1.5
  426
+
  427
+Returns the distance (float) from the origin of the geometry
  428
+(:class:`LineString` or :class:`MultiLineString`) to the point projected on the
  429
+geometry (that is to a point of the line the closest to the given point).
  430
+The normalized version returns the distance as a float between 0 (origin) and 1
  431
+(endpoint).
  432
+
  433
+Reverse of :meth:`GEOSGeometry.interpolate`.
  434
+
410 435
 .. method:: GEOSGeometry.relate(other)
411 436
 
412 437
 Returns the DE-9IM intersection matrix (a string) representing the
6  docs/releases/1.5.txt
@@ -102,6 +102,12 @@ Django 1.5 also includes several smaller improvements worth noting:
102 102
 * In the localflavor for Canada, "pq" was added to the acceptable codes for
103 103
   Quebec. It's an old abbreviation.
104 104
 
  105
+* :class:`~django.contrib.gis.geos.LineString` and
  106
+  :class:`~django.contrib.gis.geos.MultiLineString` GEOS objects now support the
  107
+  :meth:`~django.contrib.gis.geos.GEOSGeometry.interpolate()` and
  108
+  :meth:`~django.contrib.gis.geos.GEOSGeometry.project()` methods
  109
+  (so-called linear referencing).
  110
+
105 111
 Backwards incompatible changes in 1.5
106 112
 =====================================
107 113
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.