src.scooter   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 178
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 70
dl 0
loc 178
ccs 63
cts 63
cp 1
rs 10
c 0
b 0
f 0
wmc 14

12 Methods

Rating   Name   Duplication   Size   Complexity  
A Scooter.stop_scooter() 0 4 1
A Scooter.add_city_to_dict() 0 6 1
A Scooter.add_scooter_to_dict() 0 8 1
A Scooter.change_location() 0 20 1
A Scooter.check_scooter_status() 0 9 2
A Scooter.__init__() 0 2 1
A Scooter.move_to_station() 0 5 1
A Scooter.move_scooter() 0 13 1
A Scooter.__str__() 0 8 1
A Scooter.check_maintenance() 0 9 1
A Scooter.check_battery() 0 3 1
A Scooter.check_scooter_in_city() 0 15 2
1
#!/usr/bin/python3
2
3 1
"""
4
File for handling scooter data.
5
6
Scooter's status id:
7
1- Available
8
2- Unavailable
9
3- Maintenance
10
4- Charging
11
7- Running
12
13
scooter's data
14
data = {
15
    "id": "1",              # scooter id (str)
16
    "lat": 59.174586,       # coordinates (float)
17
    "lon": 17.602334,       # coordinates   (float)
18
    "speed": 0,             # km/h  (int)
19
    "battery": 0,           # % (int)
20
    "status": "7"           # status id (str)
21
    "station": "1"          # station id (str)
22
}
23
24
city's data
25
city = {
26
    "id": "2",          # citys id (str)
27
    "area": 25.84,      # km²   (float)
28
    "lat": 59.19554,    # coordinates (float)
29
    "lon": 17.62525     # coordinates (float)
30
}
31
"""
32
33 1
import math
34 1
import random
35 1
import re
36 1
from geopy.distance import geodesic, distance
37
38
39
40 1
class Scooter():
41
    """ Scooter class """
42
43
    # scooter's data
44 1
    data = {}
45
46
    # city's data
47 1
    city = {}
48
49
    # scooter's new coordinates
50 1
    location = ""
51
52
53 1
    def __init__(self) -> None:
54
        """ Initialize class """
55
56
57 1
    def __str__(self) -> str:
58
        """ Returns scooters data """
59 1
        return "Scooter id: {0}\nLocation: {1}, {2}\nSpeed: {3}km/h\nBattery: {4}%".format(
60
            self.data["id"],
61
            self.data["lat"],
62
            self.data["lon"],
63
            self.data["speed"],
64
            self.data["battery"],
65
        )
66
67
68 1
    def check_scooter_status(self, scooter: dict) -> bool:
69
        """
70
        Returns true if the scooter is available and adds the scooter's
71
        data to data dictionary.
72
        """
73 1
        if scooter["status"]["status"] == "Available":
74 1
            self.add_scooter_to_dict(scooter)
75 1
            return True
76 1
        return False
77
78
79 1
    def add_scooter_to_dict(self, scooter: dict) -> None:
80
        """ Adds scooter's data to the dictionary. Status id 7 means 'Running'. """
81 1
        self.data["id"] = scooter["id"]
82 1
        self.data["lat"] = float(scooter['latitude'])
83 1
        self.data["lon"] = float(scooter['longitude'])
84 1
        self.data["speed"] = int(scooter['speed'])
85 1
        self.data["battery"] = int(scooter['battery'])
86 1
        self.data["status"] =  "7"
87
88
89 1
    def add_city_to_dict(self, city: dict):
90
        """ Adds scooter's data to the dictionary. Status id 7 means 'Running'. """
91 1
        self.city["id"] = city["id"]
92 1
        self.city["lat"] = float(city['latitude'])
93 1
        self.city["lon"] = float(city['longitude'])
94 1
        self.city["area"] = float(city['area'])
95
96
97 1
    def move_scooter(self) -> None:
98
        """
99
        Move the scooter from one position to another and reduce battery level.
100
        Max scooter speed is 20km/h.
101
        """
102
        # get random speed
103 1
        speed = random.randrange(1, 21)
104 1
        points = re.split("Point|, ", self.location)
105
106 1
        self.data["lat"] = float(points[1][1:])
107 1
        self.data["lon"] = float(points[2])
108 1
        self.data["speed"] = speed
109 1
        self.data["battery"] -= 1
110
111
112 1
    def change_location(self) -> None:
113
        """
114
        Get random location inside the city zone.
115
        Speed = distance ÷ time => distance = speed * time
116
        Bearing in degrees: North: 0, East: 90, South: 180, West: 270.
117
        """
118
        # 5 seconds is sleep time, scooter moves every 5 seconds
119
        # but for better simulation I increase it to 15 seconds
120 1
        distance_km = self.data["speed"] * (15 / 3600)
121
122
        # get random position
123 1
        bearing = random.randint(0, 3)
124 1
        degrees = [0, 90, 180, 270]
125
126 1
        new_location = repr(distance(kilometers = distance_km).destination(
127
            (self.data["lat"], self.data["lon"]),
128
            bearing = degrees[bearing])
129
        )
130
131 1
        self.location = new_location
132
133
134 1
    def stop_scooter(self, status = "7") -> None:
135
        """ Stop the scooter from running. Change status and speed. """
136 1
        self.data["status"] = status
137 1
        self.data["speed"] = 0
138
139
140 1
    def check_battery(self) -> bool:
141
        """ Returns True if the battery level < 20%. """
142 1
        return self.data["battery"] < 20
143
144
145 1
    def move_to_station(self, station: dict) -> None:
146
        """ Move the scooter to charging/maintenance station. """
147 1
        self.data["lat"] = float(station["latitude"])
148 1
        self.data["lon"] = float(station["longitude"])
149 1
        self.data["station"] = station["id"]
150
151
152 1
    @staticmethod
153 1
    def check_maintenance() -> bool:
154
        """
155
        Returns true if the random number is 1 otherwise False, since scooters are not
156
        real, the maintenance check will be randomly.
157
        The probability that the scooter receives maintenance is 10%.
158
        """
159 1
        probability = random.randint(1, 10)
160 1
        return probability == 1
161
162
163 1
    def check_scooter_in_city(self) -> bool:
164
        """
165
        Check if scooter is inside the city zone. If the distance between
166
        two points 'city center and scooter' <= circle radius return True.
167
        """
168
        # Circle Area = pi * r^2 => r^2 = A/pi
169 1
        radius = math.sqrt((self.city["area"] / math.pi))
170 1
        scooter = (self.data["lat"], self.data["lon"])
171 1
        city = (self.city["lat"], self.city["lon"])
172
173 1
        calculate = geodesic(scooter, city).km
174
175 1
        if calculate <= radius:
176 1
            return True
177
        return False
178