33import numpy as np
44
55from .. import constants , grouping , util
6+ from ..typed import ArrayLike , Integer , NDArray , Number , Optional
67from .util import is_ccw
78
89try :
@@ -246,7 +247,7 @@ def discretize_path(entities, vertices, path, scale=1.0):
246247
247248
248249class PathSample :
249- def __init__ (self , points ):
250+ def __init__ (self , points : ArrayLike ):
250251 # make sure input array is numpy
251252 self ._points = np .array (points )
252253 # find the direction of each segment
@@ -263,7 +264,28 @@ def __init__(self, points):
263264 # note that this is sorted
264265 self ._cum_norm = np .cumsum (self ._norms )
265266
266- def sample (self , distances ):
267+ def sample (
268+ self , distances : ArrayLike , include_original : bool = False
269+ ) -> NDArray [np .float64 ]:
270+ """
271+ Return points at the distances along the path requested.
272+
273+ Parameters
274+ ----------
275+ distances
276+ Distances along the path to sample at.
277+ include_original
278+ Include the original vertices even if they are not
279+ specified in `distance`. Useful as this will return
280+ a result with identical area and length, however
281+ indexes of `distance` will not correspond with result.
282+
283+ Returns
284+ --------
285+ samples : (n, dimension)
286+ Samples requested.
287+ `n==len(distances)` if not `include_original`
288+ """
267289 # return the indices in cum_norm that each sample would
268290 # need to be inserted at to maintain the sorted property
269291 positions = np .searchsorted (self ._cum_norm , distances )
@@ -275,15 +297,35 @@ def sample(self, distances):
275297 direction = self ._unit_vec [positions ]
276298 # find out which vertex we're offset from
277299 origin = self ._points [positions ]
300+
278301 # just the parametric equation for a line
279302 resampled = origin + (direction * projection .reshape ((- 1 , 1 )))
280303
304+ if include_original :
305+ # find the insertion index of the original positions
306+ unique , index = np .unique (positions , return_index = True )
307+ # see if we already have this point
308+ ok = projection [index ] > 1e-12
309+
310+ # insert the original vertices into the resampled array
311+ resampled = np .insert (resampled , index [ok ], self ._points [unique [ok ]], axis = 0 )
312+
281313 return resampled
282314
283- def truncate (self , distance ) :
315+ def truncate (self , distance : Number ) -> NDArray [ np . float64 ] :
284316 """
285317 Return a truncated version of the path.
286318 Only one vertex (at the endpoint) will be added.
319+
320+ Parameters
321+ ----------
322+ distance
323+ Distance along the path to truncate at.
324+
325+ Returns
326+ ----------
327+ path
328+ Path clipped to `distance` requested.
287329 """
288330 position = np .searchsorted (self ._cum_norm , distance )
289331 offset = distance - self ._cum_norm [position - 1 ]
@@ -304,7 +346,13 @@ def truncate(self, distance):
304346 return truncated
305347
306348
307- def resample_path (points , count = None , step = None , step_round = True ):
349+ def resample_path (
350+ points : ArrayLike ,
351+ count : Optional [Integer ] = None ,
352+ step : Optional [Number ] = None ,
353+ step_round : bool = True ,
354+ include_original : bool = False ,
355+ ) -> NDArray [np .float64 ]:
308356 """
309357 Given a path along (n,d) points, resample them such that the
310358 distance traversed along the path is constant in between each
@@ -320,18 +368,21 @@ def resample_path(points, count=None, step=None, step_round=True):
320368 Parameters
321369 ----------
322370 points: (n, d) float
323- Points in space
371+ Points in space
324372 count : int,
325- Number of points to sample evenly (aka np.linspace)
373+ Number of points to sample evenly (aka np.linspace)
326374 step : float
327- Distance each step should take along the path (aka np.arange)
375+ Distance each step should take along the path (aka np.arange)
376+ step_round
377+ Alter `step` to the nearest integer division of overall length.
378+ include_original
379+ Include the exact original points in the output.
328380
329381 Returns
330382 ----------
331383 resampled : (j,d) float
332384 Points on the path
333385 """
334-
335386 points = np .array (points , dtype = np .float64 )
336387 # generate samples along the perimeter from kwarg count or step
337388 if (count is not None ) and (step is not None ):
@@ -351,12 +402,13 @@ def resample_path(points, count=None, step=None, step_round=True):
351402 elif step is not None :
352403 samples = np .arange (0 , sampler .length , step )
353404
354- resampled = sampler .sample (samples )
405+ resampled = sampler .sample (samples , include_original = include_original )
355406
356- check = util .row_norm (points [[0 , - 1 ]] - resampled [[0 , - 1 ]])
357- assert check [0 ] < constants .tol_path .merge
358- if count is not None :
359- assert check [1 ] < constants .tol_path .merge
407+ if constants .tol .strict :
408+ check = util .row_norm (points [[0 , - 1 ]] - resampled [[0 , - 1 ]])
409+ assert check [0 ] < constants .tol_path .merge
410+ if count is not None :
411+ assert check [1 ] < constants .tol_path .merge
360412
361413 return resampled
362414
0 commit comments