Update Path.area to work with Arc segments (#65)
* add some tests of Path.area() These tests currently fail because area() doesn't deal with Arc segments. Fix in the following commit. * make Path.area() approximate arcs Fixes #37. * Path.area(): fixup tabs/spaces for python3 * added asin to imports * added asin to imports * minor improvements to style, performance, and docstringpull/76/head
parent
e91a35c3da
commit
b37e74f5f3
|
@ -4,7 +4,7 @@ Arc."""
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
from __future__ import division, absolute_import, print_function
|
from __future__ import division, absolute_import, print_function
|
||||||
from math import sqrt, cos, sin, acos, asin, degrees, radians, log, pi
|
from math import sqrt, cos, sin, acos, asin, degrees, radians, log, pi, ceil
|
||||||
from cmath import exp, sqrt as csqrt, phase
|
from cmath import exp, sqrt as csqrt, phase
|
||||||
from collections import MutableSequence
|
from collections import MutableSequence
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
@ -2440,19 +2440,52 @@ class Path(MutableSequence):
|
||||||
# Ts += [self.t2T(i, t) for t in seg.icurvature(kappa)]
|
# Ts += [self.t2T(i, t) for t in seg.icurvature(kappa)]
|
||||||
# return Ts
|
# return Ts
|
||||||
|
|
||||||
def area(self):
|
def area(self, chord_length=1e-2):
|
||||||
"""returns the area enclosed by this Path object.
|
"""Find area enclosed by path.
|
||||||
Note: negative area results from CW (as opposed to CCW)
|
|
||||||
parameterization of the Path object."""
|
Approximates any Arc segments in the Path with lines
|
||||||
|
approximately `chord_length` long, and returns the area enclosed
|
||||||
|
by the approximated Path. Default chord length is 0.01. To
|
||||||
|
ensure accurate results, make sure this `chord_length` is set to
|
||||||
|
a reasonable value (e.g. by checking curvature).
|
||||||
|
|
||||||
|
Notes
|
||||||
|
----
|
||||||
|
* Negative area results from clockwise (as opposed to
|
||||||
|
counter-clockwise) parameterization of the input Path.
|
||||||
|
|
||||||
|
To Contributors
|
||||||
|
---------------
|
||||||
|
This is one of many parts of `svgpathtools` that could be
|
||||||
|
improved by a noble soul implementing a piecewise-linear
|
||||||
|
approximation scheme for paths (one with controls to
|
||||||
|
guarantee a desired accuracies).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def area_without_arcs(self):
|
||||||
|
area_enclosed = 0
|
||||||
|
for seg in self:
|
||||||
|
x = real(seg.poly())
|
||||||
|
dy = imag(seg.poly()).deriv()
|
||||||
|
integrand = x*dy
|
||||||
|
integral = integrand.integ()
|
||||||
|
area_enclosed += integral(1) - integral(0)
|
||||||
|
return area_enclosed
|
||||||
|
|
||||||
assert self.isclosed()
|
assert self.isclosed()
|
||||||
area_enclosed = 0
|
|
||||||
|
bezier_path_approximation = Path()
|
||||||
for seg in self:
|
for seg in self:
|
||||||
x = real(seg.poly())
|
if isinstance(seg, Arc):
|
||||||
dy = imag(seg.poly()).deriv()
|
num_lines = ceil(seg.length() / chord_length) # check curvature to improve
|
||||||
integrand = x*dy
|
bezier_path_approximation = \
|
||||||
integral = integrand.integ()
|
[Line(seg.point(i/num_lines), seg.point((i+1)/num_lines))
|
||||||
area_enclosed += integral(1) - integral(0)
|
for i in range(int(num_lines))]
|
||||||
return area_enclosed
|
else:
|
||||||
|
approximated_path.append(seg)
|
||||||
|
|
||||||
|
return area_without_arcs(approximated_path)
|
||||||
|
|
||||||
|
|
||||||
def intersect(self, other_curve, justonemode=False, tol=1e-12):
|
def intersect(self, other_curve, justonemode=False, tol=1e-12):
|
||||||
"""returns list of pairs of pairs ((T1, seg1, t1), (T2, seg2, t2))
|
"""returns list of pairs of pairs ((T1, seg1, t1), (T2, seg2, t2))
|
||||||
|
|
|
@ -1747,5 +1747,33 @@ class TestPathTools(unittest.TestCase):
|
||||||
# openinbrowser=True)
|
# openinbrowser=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_area(self):
|
||||||
|
cw_square = Path()
|
||||||
|
cw_square.append(Line((0+0j), (0+100j)))
|
||||||
|
cw_square.append(Line((0+100j), (100+100j)))
|
||||||
|
cw_square.append(Line((100+100j), (100+0j)))
|
||||||
|
cw_square.append(Line((100+0j), (0+0j)))
|
||||||
|
self.assertEqual(cw_square.area(), -10000.0)
|
||||||
|
|
||||||
|
ccw_square = Path()
|
||||||
|
ccw_square.append(Line((0+0j), (100+0j)))
|
||||||
|
ccw_square.append(Line((100+0j), (100+100j)))
|
||||||
|
ccw_square.append(Line((100+100j), (0+100j)))
|
||||||
|
ccw_square.append(Line((0+100j), (0+0j)))
|
||||||
|
self.assertEqual(ccw_square.area(), 10000.0)
|
||||||
|
|
||||||
|
cw_half_circle = Path()
|
||||||
|
cw_half_circle.append(Line((0+0j), (0+100j)))
|
||||||
|
cw_half_circle.append(Arc(start=(0+100j), radius=(50+50j), rotation=0, large_arc=False, sweep=False, end=(0+0j)))
|
||||||
|
self.assertAlmostEqual(cw_half_circle.area(), -3926.9908169872415, places=3)
|
||||||
|
self.assertAlmostEqual(cw_half_circle.area(chord_length=1e-3), -3926.9908169872415, places=6)
|
||||||
|
|
||||||
|
ccw_half_circle = Path()
|
||||||
|
ccw_half_circle.append(Line((0+100j), (0+0j)))
|
||||||
|
ccw_half_circle.append(Arc(start=(0+0j), radius=(50+50j), rotation=0, large_arc=False, sweep=True, end=(0+100j)))
|
||||||
|
self.assertAlmostEqual(ccw_half_circle.area(), 3926.9908169872415, places=3)
|
||||||
|
self.assertAlmostEqual(ccw_half_circle.area(chord_length=1e-3), 3926.9908169872415, places=6)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue