예제 #1
0
    def end_service(self, devices, end_time, locations=None):
        """
        Create service_change `removed:service_end` events for each of :devices:.

        If :locations: are provided, use the corresponding location for each device.
        """
        service_changes = []

        # device pickup likely doesn't happen right at close time
        # +7200 seconds == next 2 hours after close
        offset = timedelta(seconds=7200)
        for device in devices:
            # somewhere in the next :offset:
            event_time = random_date_from(end_time, max_td=offset)

            # use the device's index for the locations if provided
            # otherwise generate a random event_location
            if locations is None:
                point = point_within(self.boundary)
            else:
                point = extract_point(locations[devices.index(device)])

            # the service_change details
            feature = to_feature(point, properties=dict(timestamp=event_time))
            service_end = \
                self.status_change_event(device,
                                         event_type="removed",
                                         event_type_reason="service_end",
                                         event_time=event_time,
                                         event_location=feature)

            # combine with device details and append
            service_changes.append({**device, **service_end})

        return service_changes
예제 #2
0
    def devices_recharged(self, devices, event_times, event_locations=None):
        """
        Create a `available:maintenance_drop_off` status change event for each of :devices:.

        :event_times: is an single or list of datetimes:
            - single datetime: use this as a reference to produce a random event time within the given hour
            - list with len == len(devices): use the corresponding event_time for each device

        :event_locations: is an (optional) single or list of locations:
            - None: generate a random dropoff location for each device
            - single location: use this as the dropoff location
            - list with len == len(devices): use the corresponding event_location for each device
        """
        service_changes = []

        for device in devices:
            if isinstance(event_times, datetime):
                # how many seconds until the next hour?
                diff = (60 - event_times.minute - 1) * 60 + (
                    60 - event_times.second)
                # random datetime between event_times and then
                event_time = random_date_from(event_times,
                                              max_td=timedelta(seconds=diff))
            elif len(event_times) == len(devices):
                # corresponding datetime
                event_time = event_times[devices.index(device)]

            if event_locations is None:
                # random point
                point = point_within(self.boundary)
                event_location = to_feature(
                    point, properties=dict(timestamp=event_time))
            elif len(event_locations) == len(devices):
                # corresponding location
                event_location = event_locations[devices.index(device)]
            else:
                # given location
                event_location = event_locations

            # create the event for this device
            service_changes.append(
                self.device_recharged(device, event_time, event_location))

        return service_changes
예제 #3
0
    def start_service(self, devices, start_time):
        """
        Create service_change `available:service_start` events on or around :starttime:
        for each of the :devices:.
        """
        service_changes = []

        # device placement starts before operation open time
        # -7200 seconds == previous 2 hours from start
        offset = timedelta(seconds=-7200)
        for device in devices:
            # somewhere in the previous :offset:
            event_time = random_date_from(start_time, min_td=offset)
            point = point_within(self.boundary)
            feature = to_feature(point, properties=dict(timestamp=event_time))

            # the service_change details
            service_start = \
                self.status_change_event(device,
                                         event_type="available",
                                         event_type_reason="service_start",
                                         event_time=event_time,
                                         event_location=feature)

            # maybe add an empty associated_trips array
            if random.choice([False, True]):
                service_start["associated_trips"] = random.choice([None, []])

            # reset the battery for electric devices
            if self.has_battery(device):
                self.recharge_battery(device)

            # combine with device details and append
            service_changes.append({**device, **service_start})

        return service_changes
예제 #4
0
    def device_trip(self,
                    device,
                    event_time=None,
                    event_location=None,
                    end_location=None,
                    reference_time=None,
                    min_td=timedelta(seconds=0),
                    max_td=timedelta(seconds=0),
                    speed=None):
        """
        Create a trip and associated status changes for the given :device:.

        :event_time: is the time the trip should start

        :event_location: is the location (Feature) the trip should start
        
        :end_location: is the location (Feature) the trip should end
        
        :reference_time: is the 0-point around which to calculate a random start time
            - :min_td: the minimum time from :reference_time:
            - :max_td: the maximum time from :reference_time:

        :speed: is the average speed of the device in meters/second

        Returns a tuple:
            - the list of status changes
            - the trip
        """
        if (event_time is None) and (reference_time is not None):
            event_time = random_date_from(reference_time,
                                          min_td=min_td,
                                          max_td=max_td)

        if event_location is None:
            point = point_within(self.boundary)
            event_location = to_feature(point,
                                        properties=dict(timestamp=event_time))

        if speed is None:
            speed = self.speed

        # begin the trip
        status_changes = [self.start_trip(device, event_time, event_location)]

        # random trip duration (in seconds)
        # the gamma distribution is referenced in the literature,
        # see: https://static.tti.tamu.edu/tti.tamu.edu/documents/17-1.pdf
        # experimenting with the scale factors led to these parameterizations, * 60 to get seconds
        alpha, beta = 3, 4.5
        trip_duration = random.gammavariate(alpha, beta) * 60

        # account for traffic, turns, etc.
        trip_distance = trip_duration * speed * 0.8

        # Model the accuracy as a rayleigh distribution with median ~5m
        accuracy = scipy.stats.rayleigh.rvs(scale=5)

        # drain the battery according to the speed and distance traveled
        if self.has_battery(device):
            amount = speed / 100
            rate = trip_distance / (math.sqrt(trip_distance) * 200)
            self.drain_battery(device, amount=amount, rate=rate)

        # calculate out the ending time and location
        end_time = event_time + timedelta(seconds=trip_duration)
        if end_location is None:
            start_point = extract_point(event_location)
            end_point = point_nearby(start_point, trip_distance)
            end_location = to_feature(end_point,
                                      properties=dict(timestamp=end_time))

        # generate the route object
        route = self.trip_route(event_location, end_location)

        # and finally the trip object
        trip = dict(accuracy=int(accuracy),
                    trip_id=uuid.uuid4(),
                    trip_duration=int(trip_duration),
                    trip_distance=int(trip_distance),
                    route=route,
                    start_time=event_time,
                    end_time=end_time)

        # add a parking_verification_url?
        if random.choice([True, False]):
            trip.update(parking_verification_url=random_file_url(
                device["provider_name"]))

        # add a standard_cost?
        if random.choice([True, False]):
            # $1.00 to start and $0.15 a minute thereafter
            trip.update(
                standard_cost=(100 +
                               (math.floor(trip_duration / 60) - 1) * 15))

        # add an actual cost?
        if random.choice([True, False]):
            # randomize an actual_cost
            # $0.75 - $1.50 to start, and $0.12 - $0.20 a minute thereafter...
            start, rate = random.randint(75, 150), random.randint(12, 20)
            trip.update(
                actual_cost=(start +
                             (math.floor(trip_duration / 60) - 1) * rate))

        # end the trip
        status_changes.append(self.end_trip(device, end_time, end_location))

        # merge the device info into the trip
        trip = {**device, **trip}

        # cleanup leftover fields not part of a trip
        if BATTERY in trip:
            del trip[BATTERY]
        if EVENT_TIME in trip:
            del trip[EVENT_TIME]

        # return a list of the status changes and the trip
        return [{**device, **sc} for sc in status_changes], trip