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
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
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
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