Esempio n. 1
0
class _OdometerShape(object):

    # A 0.1% cone - Coefficient to defavor points further away on a shape
    # when doing stop snapping to shape. The summit angle of the offset cone
    # is 2 * arctan(K).
    K = 0.001

    def __init__(self, shape):
        self._shape = shape
        self._cache = _CacheEntry(None)
        self._cache_hit = 0
        self._cache_miss = 0
        if all(pt.shape_dist_traveled != -999999 for pt in shape.points):
            self._xdist = ContinousPiecewiseLinearFunc()
        else:
            self._xdist = None
        # Normalize the shape here:
        # 1) dist_traveled to meters
        # 2) pt_seq to contiguous numbering from 0
        ptseq = 0
        distance_meters = 0.0
        last_pt = None
        shape.points.sort(key=lambda p: p.shape_pt_sequence)
        for pt in shape.points:
            if last_pt is not None:
                # Note: we do not use distance cache, as most probably
                # many of the points will be different from each other.
                distance_meters += orthodromic_distance(last_pt, pt)
            last_pt = pt
            pt.shape_pt_sequence = ptseq
            old_distance = pt.shape_dist_traveled
            pt.shape_dist_traveled = distance_meters
            # Remember the distance mapping for stop times
            if self._xdist:
                self._xdist.append(old_distance, pt.shape_dist_traveled)
            ptseq += 1

    def reset(self):
        self._distance = 0
        self._istart = 0
        self._cache_cursor = self._cache

    def dist_traveled(self, stop, old_dist_traveled):
        if old_dist_traveled and self._xdist:
            # Case 1: we have in the original data shape_dist_traveled
            # We need to remap from the old scale to the new meter scale
            return self._xdist.interpolate(old_dist_traveled)
        else:
            # Case 2: we do not have original shape_dist_traveled
            # We need to determine ourselves where in the shape we lie
            # TODO Implement a cache, this can be slow for lots of trips
            # and the result is the same for the same pattern
            # Check the cache first
            cache_entry = self._cache_cursor.next_entry(stop)
            if cache_entry is not None:
                self._cache_cursor = cache_entry
                self._cache_hit += 1
                return cache_entry.distance()
            min_dist = 1e20
            best_i = self._istart
            best_dist = 0
            for i in range(self._istart, len(self._shape.points) - 1):
                a = self._shape.points[i]
                b = self._shape.points[i + 1]
                dist, pdist = orthodromic_seg_distance(stop, a, b)
                newdist = a.shape_dist_traveled + pdist
                howfar = newdist - self._distance
                # Add a slight "cone" offset. There are pathological
                # cases with backtracking shapes where the best distance
                # is slightly better way further (for eg 0.01m) than at
                # the starting point (for eg 0.02m). In that case we should
                # obviously keep the first point instead of moving too fast
                # to the shape end. That offset should help for some cases.
                dist += howfar * self.K
                if dist < min_dist:
                    min_dist = dist
                    best_i = i
                    best_dist = newdist
            if best_dist > self._distance:
                self._distance = best_dist
            else:
                delta = self._distance - best_dist
                if delta > 10:
                    # This is harmless if the backtracking distance is small.
                    # We have lots of false positive (<<1m) due to rounding errors.
                    logger.warn(
                        "Backtracking of %f m detected in shape %s for stop %s (%s) (%f,%f) at distance %f < %f m on segment #[%d-%d]"
                        % (delta, self._shape.shape_id, stop.stop_id,
                           stop.stop_name, stop.stop_lat, stop.stop_lon,
                           best_dist, self._distance, best_i, best_i + 1))
            self._istart = best_i
            self._cache_miss += 1
            self._cache_cursor = self._cache_cursor.insert(
                stop, self._distance)
            return self._distance

    def _debug_cache(self):
        logger.debug("Shape %s: Cache hit: %d, misses: %d" %
                     (self._shape.shape_id, self._cache_hit, self._cache_miss))
Esempio n. 2
0
class _OdometerShape(object):

    # A 0.1% cone - Coefficient to defavor points further away on a shape
    # when doing stop snapping to shape. The summit angle of the offset cone
    # is 2 * arctan(K).
    K = 0.001

    def __init__(self, shape):
        self._shape = shape
        self._cache = _CacheEntry(None)
        self._cache_hit = 0
        self._cache_miss = 0
        if all(pt.shape_dist_traveled != -999999 for pt in shape.points):
            self._xdist = ContinousPiecewiseLinearFunc()
        else:
            self._xdist = None
        # Normalize the shape here:
        # 1) dist_traveled to meters
        # 2) pt_seq to contiguous numbering from 0
        ptseq = 0
        distance_meters = 0.0
        last_pt = None
        shape.points.sort(key=lambda p: p.shape_pt_sequence)
        for pt in shape.points:
            if last_pt is not None:
                # Note: we do not use distance cache, as most probably
                # many of the points will be different from each other.
                distance_meters += orthodromic_distance(last_pt, pt)
            last_pt = pt
            pt.shape_pt_sequence = ptseq
            old_distance = pt.shape_dist_traveled
            pt.shape_dist_traveled = distance_meters
            # Remember the distance mapping for stop times
            if self._xdist:
                self._xdist.append(old_distance, pt.shape_dist_traveled)
            ptseq += 1

    def reset(self):
        self._distance = 0
        self._istart = 0
        self._cache_cursor = self._cache

    def dist_traveled(self, stop, old_dist_traveled):
        if old_dist_traveled and self._xdist:
            # Case 1: we have in the original data shape_dist_traveled
            # We need to remap from the old scale to the new meter scale
            return self._xdist.interpolate(old_dist_traveled)
        else:
            # Case 2: we do not have original shape_dist_traveled
            # We need to determine ourselves where in the shape we lie
            # TODO Implement a cache, this can be slow for lots of trips
            # and the result is the same for the same pattern
            # Check the cache first
            cache_entry = self._cache_cursor.next_entry(stop)
            if cache_entry is not None:
                self._cache_cursor = cache_entry
                self._cache_hit += 1
                return cache_entry.distance()
            min_dist = 1e20
            best_i = self._istart
            best_dist = 0
            for i in range(self._istart, len(self._shape.points) - 1):
                a = self._shape.points[i]
                b = self._shape.points[i+1]
                dist, pdist = orthodromic_seg_distance(stop, a, b)
                newdist = a.shape_dist_traveled + pdist
                howfar = newdist - self._distance
                # Add a slight "cone" offset. There are pathological
                # cases with backtracking shapes where the best distance
                # is slightly better way further (for eg 0.01m) than at
                # the starting point (for eg 0.02m). In that case we should
                # obviously keep the first point instead of moving too fast
                # to the shape end. That offset should help for some cases.
                dist += howfar * self.K
                if dist < min_dist:
                    min_dist = dist
                    best_i = i
                    best_dist = newdist
            if best_dist > self._distance:
                self._distance = best_dist
            else:
                delta = self._distance - best_dist
                if delta > 10:
                    # This is harmless if the backtracking distance is small.
                    # We have lots of false positive (<<1m) due to rounding errors.
                    logger.warn("Backtracking of %f m detected in shape %s for stop %s (%s) (%f,%f) at distance %f < %f m on segment #[%d-%d]" % (
                                delta, self._shape.shape_id, stop.stop_id, stop.stop_name, stop.stop_lat, stop.stop_lon, best_dist, self._distance, best_i, best_i+1))
            self._istart = best_i
            self._cache_miss += 1
            self._cache_cursor = self._cache_cursor.insert(stop, self._distance)
            return self._distance

    def _debug_cache(self):
        logger.debug("Shape %s: Cache hit: %d, misses: %d" % (self._shape.shape_id, self._cache_hit, self._cache_miss))
Esempio n. 3
0
    def test_piecewise_linear_interpolation(self):

        # Empty function
        f = ContinousPiecewiseLinearFunc()
        catched = False
        try:
            self.assertAlmostEqual(f.interpolate(0), 0, 3)
        except:
            catched = True
        self.assertTrue(catched)

        # Single point function
        f = ContinousPiecewiseLinearFunc()
        f.append(0, 0)
        self.assertAlmostEqual(f.interpolate(0), 0, 6)
        self.assertAlmostEqual(f.interpolate(-1), 0, 6)
        self.assertAlmostEqual(f.interpolate(1), 0, 6)

        # A simple well-behaved function
        f = ContinousPiecewiseLinearFunc()
        f.append(0, 0)
        f.append(1, 100)
        self.assertAlmostEqual(f.interpolate(-0.000000001), 0, 6)
        self.assertAlmostEqual(f.interpolate(0), 0, 6)
        self.assertAlmostEqual(f.interpolate(0.000000001), 0, 6)
        self.assertAlmostEqual(f.interpolate(0.5), 50.0, 6)
        self.assertAlmostEqual(f.interpolate(0.9999999999), 100, 6)
        self.assertAlmostEqual(f.interpolate(1.0), 100, 6)
        self.assertAlmostEqual(f.interpolate(1.0000000001), 100, 6)

        # Function with double-point (infinite derivative)
        f = ContinousPiecewiseLinearFunc()
        f.append(0, 0)
        f.append(1, 0)
        f.append(1, 100)
        f.append(2, 100)
        self.assertAlmostEqual(f.interpolate(0), 0, 6)
        self.assertAlmostEqual(f.interpolate(1), 100, 6)
        self.assertAlmostEqual(f.interpolate(-1), 0, 6)

        # A more complex one
        f = ContinousPiecewiseLinearFunc()
        f.append(10, 9000)
        f.append(20, 10000)
        f.append(1000, 10980)
        self.assertAlmostEqual(f.interpolate(5), 9000.0, 6)
        self.assertAlmostEqual(f.interpolate(15), 9500.0, 6)
        self.assertAlmostEqual(f.interpolate(40), 10020.0, 3)
        self.assertAlmostEqual(f.interpolate(1010), 10980.0, 3)
Esempio n. 4
0
    def test_piecewise_linear_interpolation(self):

        # Empty function
        f = ContinousPiecewiseLinearFunc()
        catched = False
        try:
            self.assertAlmostEqual(f.interpolate(0), 0, 3)
        except:
            catched = True
        self.assertTrue(catched)

        # Single point function
        f = ContinousPiecewiseLinearFunc()
        f.append(0, 0)
        self.assertAlmostEqual(f.interpolate(0), 0, 6)
        self.assertAlmostEqual(f.interpolate(-1), 0, 6)
        self.assertAlmostEqual(f.interpolate(1), 0, 6)

        # A simple well-behaved function
        f = ContinousPiecewiseLinearFunc()
        f.append(0, 0)
        f.append(1, 100)
        self.assertAlmostEqual(f.interpolate(-0.000000001), 0, 6)
        self.assertAlmostEqual(f.interpolate(0), 0, 6)
        self.assertAlmostEqual(f.interpolate(0.000000001), 0, 6)
        self.assertAlmostEqual(f.interpolate(0.5), 50.0, 6)
        self.assertAlmostEqual(f.interpolate(0.9999999999), 100, 6)
        self.assertAlmostEqual(f.interpolate(1.0), 100, 6)
        self.assertAlmostEqual(f.interpolate(1.0000000001), 100, 6)

        # Function with double-point (infinite derivative)
        f = ContinousPiecewiseLinearFunc()
        f.append(0, 0)
        f.append(1, 0)
        f.append(1, 100)
        f.append(2, 100)
        self.assertAlmostEqual(f.interpolate(0), 0, 6)
        self.assertAlmostEqual(f.interpolate(1), 100, 6)
        self.assertAlmostEqual(f.interpolate(-1), 0, 6)

        # A more complex one
        f = ContinousPiecewiseLinearFunc()
        f.append(10, 9000)
        f.append(20, 10000)
        f.append(1000, 10980)
        self.assertAlmostEqual(f.interpolate(5), 9000.0, 6)
        self.assertAlmostEqual(f.interpolate(15), 9500.0, 6)
        self.assertAlmostEqual(f.interpolate(40), 10020.0, 3)
        self.assertAlmostEqual(f.interpolate(1010), 10980.0, 3)