Esempio n. 1
0
    def devices_recharged(self, devices, event_times, event_locations=None):
        """
        Create a available:maintenance_drop_off status_change event for each device.

        Parameters:
            devices: list
                The list of devices to recharge.

            event_times: datetime, list
                datetime: a reference to produce a random event time within the given hour.
                len(list) == len(devices): use the corresponding event_time for each device.

            event_locations: GeoJSON Feature, list, optional
                None: generate a random dropoff location for each device.
                Feature: use this as the dropoff location.
                len(list) == len(devices): use the corresponding event_location for each device.

        Returns:
            list
                A list of available:maintenance_drop_off status_change events.
        """
        status_changes = []

        for device in devices:
            if isinstance(event_times, datetime.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 = util.random_date_from(
                    event_times, max_td=datetime.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 = geometry.point_within(self.boundary)
                event_location = mds.geometry.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
            status_changes.append(
                self.device_recharged(device, event_time, event_location))

        return status_changes
Esempio n. 2
0
    def status_change_event(self,
                            device,
                            event_type=None,
                            event_type_reason=None,
                            event_time=None,
                            event_location=None,
                            **kwargs):
        """
        Create a status_change event from the provided data.

        Parameters:
            device: dict
                The device that generated the event.

            event_type: str, optional
                The type of status_change event.

            event_type_reason: str, optional
                The reason for this type of status_change event.

            event_time: datetime, optional
                The time when the event occurred.

            event_location: GeoJSON Feature, optional
                The location where the event occurred.

            Additional keyword parameters are passed into the event as attributes.

        Returns:
            dict
                A dict representation of the status_change data.
        """
        if event_type is None or event_type_reason is None:
            event_type, event_type_reason = self.event_pair(
                event_type, event_type_reason)

        if event_time is None:
            event_type = util.random_date_from(datetime.datetime.utcnow())

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

        status_change = dict(event_type=event_type,
                             event_type_reason=event_type_reason,
                             event_time=event_time,
                             event_location=event_location,
                             publication_time=event_time)

        return {**device, **status_change, **kwargs}
Esempio n. 3
0
    def end_service(self, devices, end_time, locations=None):
        """
        Create status_change removed:service_end events.

        Parameters:
            devices: list
                The list of devices to bring into service.

            end_time: datetime
                The approximate time of service end.

            locations: list, optional
                The corresponding end location for each device. By default, generate a random location.

        Returns:
            list
                The list of status_change events.
        """
        status_changes = []

        # device pickup likely doesn't happen right at close time
        # +7200 seconds == next 2 hours after close
        offset = datetime.timedelta(seconds=7200)

        for device in devices:
            # somewhere in the next :offset:
            event_time = util.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 = geometry.point_within(self.boundary)
            else:
                point = mds.geometry.extract_point(
                    locations[devices.index(device)])

            # the status_change details
            feature = mds.geometry.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
            status_changes.append({**device, **service_end})

        return status_changes
Esempio n. 4
0
    def start_service(self, devices, start_time):
        """
        Create status_change available:service_start events.

        Parameters:
            devices: list
                The list of devices to bring into service.

            start_time: datetime
                The approximate time of service start.

        Return:
            list
                The list of service_start events.
        """
        status_changes = []

        # device placement starts before operation open time
        # -7200 seconds == previous 2 hours from start
        offset = datetime.timedelta(seconds=-7200)

        for device in devices:
            # somewhere in the previous :offset:
            event_time = util.random_date_from(start_time, min_td=offset)
            point = geometry.point_within(self.boundary)
            feature = mds.geometry.to_feature(
                point, properties=dict(timestamp=event_time))

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

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

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

        return status_changes
Esempio n. 5
0
    def device_trip(self, device, event_time=None, event_location=None,
                    end_location=None, reference_time=None, min_td=datetime.timedelta(seconds=0),
                    max_td=datetime.timedelta(seconds=0), speed=None):
        """
        Create a trip and associated status_changes for a device.

        Parameters:
            device: dict
                The device that will take the trip.

            event_time: datetime, optional
                The time the trip should start.

            event_location: GeoJSON Feature, optional
                The location the trip should start.

            end_location: GeoJSON Feature, optional
                The location the trip should end.

            reference_time: datetime, optional
                The 0-point around which to calculate a random start time.

            min_td: timedelta, optional
                The minimum time in the past from reference_time.

            max_td: timedelta, optional
                The maximum time in the future of reference_time.

            speed: int, optional
                The average speed of the device in meters/second.

        Returns:
            tuple (status_changes: list, trip: dict)
        """
        if (event_time is None) and (reference_time is not None):
            event_time = util.random_date_from(reference_time, min_td=min_td, max_td=max_td)

        if event_location is None:
            point = geometry.point_within(self.boundary)
            event_location = mds.geometry.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 + datetime.timedelta(seconds=trip_duration)
        if end_location is None:
            start_point = mds.geometry.extract_point(event_location)
            end_point = geometry.point_nearby(start_point, trip_distance, boundary=self.boundary)
            end_location = mds.geometry.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
        )

        if self.version >= Version("0.3.0"):
            trip[PUBLICATION_TIME] = end_time

        # add a parking_verification_url?
        if random.choice([True, False]):
            trip.update(parking_verification_url=util.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
Esempio n. 6
0
    def vehicle_status(self,
                       device,
                       event_type=None,
                       event_type_reason=None,
                       event_time=None,
                       event_location=None,
                       current_location=None,
                       battery_pct=None,
                       **kwargs):
        """
        Create a status event compatible with the vehicles endpoint.

        Parameters:
            device: dict
                The device that generated the event.

            event_type: str, optional
                Event type of most recent status change.

            event_type_reason: str, optional
                Event type reason of most recent status change.

            event_time: datetime, optional
                Date/time when last status change occurred.

            event_location: GeoJSON Feature, optional
                Location of vehicle's last event.

            current_location: GeoJSON Feature, optional
                Current location of vehicle if different from last event.

            battery_pct: decimal, optional
                Percent battery charge of device, expressed between 0 and 1.

            Additional keyword parameters are passed into the vehicle as attributes.

        Returns:
            dict
                A dict representation of the vehicle status data.
        """
        if event_type is None or event_type_reason is None:
            event_type, event_type_reason = self.event_pair(
                event_type, event_type_reason)

        if event_time is None:
            event_time = util.random_date_from(datetime.datetime.utcnow())

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

        last_status = dict(last_event_time=event_time,
                           last_event_type=event_type,
                           last_event_type_reason=event_type_reason,
                           last_event_location=event_location)

        if current_location:
            last_status.update(current_location=current_location)

        if battery_pct is not None:
            last_status.update(battery_pct=battery_pct)

        return {**device, **last_status, **kwargs}