-
Notifications
You must be signed in to change notification settings - Fork 0
/
models.py
177 lines (146 loc) · 6.73 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""Represent models for near-Earth objects and their close approaches.
The `NearEarthObject` class represents a near-Earth object. Each has a unique
primary designation, an optional unique name, an optional diameter, and a flag
for whether the object is potentially hazardous.
The `CloseApproach` class represents a close approach to Earth by an NEO. Each
has an approach datetime, a nominal approach distance, and a relative approach
velocity.
A `NearEarthObject` maintains a collection of its close approaches, and a
`CloseApproach` maintains a reference to its NEO.
The functions that construct these objects use information extracted from the
data files from NASA, so these objects should be able to handle all of the
quirks of the data set, such as missing names and unknown diameters.
You'll edit this file in Task 1.
"""
from helpers import cd_to_datetime, datetime_to_str
import math
class NearEarthObject:
"""A near-Earth object (NEO).
An NEO encapsulates semantic and physical parameters about the object, such
as its primary designation (required, unique), IAU name (optional),
diameter in kilometers (optional - sometimes unknown), and whether it's
marked as potentially hazardous to Earth.
A `NearEarthObject` also maintains a collection of its close approaches -
initialized to an empty collection, but eventually populated in the
`NEODatabase` constructor.
"""
def __init__(self, designation='', name=None, hazardous=False,
diameter=float('nan')):
"""Create a new `NearEarthObject`.
:param info: A dictionary of excess keyword arguments supplied to the
constructor.
"""
self.name = None if name == '' else name
self.designation = designation
self.diameter = float(diameter) if diameter != '' else float('nan')
self.hazardous = True if hazardous == 'Y' else False
# Create an empty initial collection of linked approaches.
self.approaches = []
@property
def fullname(self):
"""Returns the full name of the NEO."""
if self.name:
return f"{self.designation} ({self.name})"
else:
return f"{self.designation}"
def __str__(self):
"""Return `str(self)`."""
if not math.isnan(self.diameter):
return f"NEO {self.fullname} has a diameter of " \
f"{self.diameter:.3f} km and " \
f"{'is' if self.hazardous else 'is not'} " \
f"potentially hazardous."
else:
return f"NEO {self.fullname}, " \
f"{'is' if self.hazardous else 'is not'} potentially " \
f"hazardous."
def __repr__(self):
"""Return `repr(self)`, a computer-readable string representation
of this object."""
return f"NearEarthObject(designation={self.designation!r}, " \
f"name={self.name!r}, diameter={self.diameter:.3f}, " \
f"hazardous={self.hazardous!r})"
class CloseApproach:
"""A close approach to Earth by an NEO.
A `CloseApproach` encapsulates information about the NEO's close approach
to Earth, such as the date and time (in UTC) of closest approach, the
nominal approach distance in astronomical units, and the relative
approach velocity in kilometers per second.
A `CloseApproach` also maintains a reference to its `NearEarthObject` -
initially, this information (the NEO's primary designation) is saved in a
private attribute, but the referenced NEO is eventually replaced in the
`NEODatabase` constructor.
"""
def __init__(self, neo=None, velocity=0.0, distance=0.0,
time='0001-Jan-01 00:00'):
"""Create a new `CloseApproach`.
:param info: A dictionary of excess keyword arguments supplied to
the constructor.
"""
self._designation = neo
self.time = cd_to_datetime(time)
self.distance = float(distance)
self.velocity = float(velocity)
# Create an attribute for the referenced NEO, originally None.
self.neo = None
@property
def time_str(self):
"""Return a formatted representation of this `CloseApproach`'s
approach time.
The value in `self.time` should be a Python `datetime` object. While a
`datetime` object has a string representation, the default
representation includes seconds - significant figures that don't exist
in our input data set.
The `datetime_to_str` method converts a `datetime` object to a
formatted string that can be used in human-readable representations and
in serialization to CSV and JSON files.
"""
if self.time:
return datetime_to_str(self.time)
return "an unspecified date and time"
@property
def designation(self):
"""Designation getter"""
return self._designation
def __str__(self):
"""Return `str(self)`."""
return f"At {self.time_str}, '{self.neo.fullname}' approached " \
f"Earth at a distance of {self.distance:.2f} au and a " \
f"velocity of {self.velocity:.2f} km/s."
def __repr__(self):
"""Return `repr(self)`, a computer-readable string representation
of this object."""
return f"CloseApproach(time={self.time_str!r}, " \
f"distance={self.distance:.2f}, " \
f"velocity={self.velocity:.2f}, " \
f"neo={self.neo!r})"
def serialize(self, file_extension: str) -> dict:
"""Converts JSON or CSV file date into a dictionary of serialized
data.
:params: file_extension accepts CSV and JSON
:return: the data in a serialised dictionary
"""
if file_extension == 'json':
data = {'neo': {'name': self.neo.name,
'designation': self.neo.designation,
'potentially_hazardous':
self.neo.hazardous,
'diameter_km': self.neo.diameter,
},
'datetime_utc': self.time_str,
'velocity_km_s': self.velocity,
'distance_au': self.distance,
}
return data
elif file_extension == 'csv':
data = {'name': self.neo.name,
'designation': self.neo.designation,
'datetime_utc': self.time_str,
'diameter_km': self.neo.diameter,
'potentially_hazardous': self.neo.hazardous,
'velocity_km_s': self.velocity,
'distance_au': self.distance,
}
return data
else:
raise ValueError('Invalid file extension')