Update Exploring Near Earth Objects project and add Meme Generator project
This commit is contained in:
@@ -21,6 +21,7 @@ class NEODatabase:
|
||||
help fetch NEOs by primary designation or by name and to help speed up
|
||||
querying for close approaches that match criteria.
|
||||
"""
|
||||
|
||||
def __init__(self, neos, approaches):
|
||||
"""Create a new `NEODatabase`.
|
||||
|
||||
@@ -42,9 +43,16 @@ class NEODatabase:
|
||||
self._neos = neos
|
||||
self._approaches = approaches
|
||||
|
||||
# TODO: What additional auxiliary data structures will be useful?
|
||||
self._neo_by_designation = {neo.designation: neo for neo in self._neos}
|
||||
self._neo_by_name = {neo.name: neo for neo in self._neos}
|
||||
|
||||
# TODO: Link together the NEOs and their close approaches.
|
||||
for approach in self._approaches:
|
||||
# Link approach.neo to the corresponding NearEarthObject, namely
|
||||
# the one whose designation matches approach._designation.
|
||||
approach.neo = self._neo_by_designation[approach._designation]
|
||||
# Add this approach to the corresponding NearEarthObject's
|
||||
# .approaches collection.
|
||||
self._neo_by_designation[approach._designation].approaches.append(approach)
|
||||
|
||||
def get_neo_by_designation(self, designation):
|
||||
"""Find and return an NEO by its primary designation.
|
||||
@@ -59,8 +67,7 @@ class NEODatabase:
|
||||
:param designation: The primary designation of the NEO to search for.
|
||||
:return: The `NearEarthObject` with the desired primary designation, or `None`.
|
||||
"""
|
||||
# TODO: Fetch an NEO by its primary designation.
|
||||
return None
|
||||
return self._neo_by_designation.get(designation, None)
|
||||
|
||||
def get_neo_by_name(self, name):
|
||||
"""Find and return an NEO by its name.
|
||||
@@ -76,8 +83,7 @@ class NEODatabase:
|
||||
:param name: The name, as a string, of the NEO to search for.
|
||||
:return: The `NearEarthObject` with the desired name, or `None`.
|
||||
"""
|
||||
# TODO: Fetch an NEO by its name.
|
||||
return None
|
||||
return self._neo_by_name.get(name, None)
|
||||
|
||||
def query(self, filters=()):
|
||||
"""Query close approaches to generate those that match a collection of filters.
|
||||
@@ -93,6 +99,7 @@ class NEODatabase:
|
||||
:param filters: A collection of filters capturing user-specified criteria.
|
||||
:return: A stream of matching `CloseApproach` objects.
|
||||
"""
|
||||
# TODO: Generate `CloseApproach` objects that match all of the filters.
|
||||
for approach in self._approaches:
|
||||
yield approach
|
||||
cases = [filter_i(approach) for filter_i in filters]
|
||||
if all(cases):
|
||||
yield approach
|
||||
|
||||
@@ -12,9 +12,9 @@ line, and uses the resulting collections to build an `NEODatabase`.
|
||||
|
||||
You'll edit this file in Task 2.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import json
|
||||
|
||||
from models import NearEarthObject, CloseApproach
|
||||
|
||||
|
||||
@@ -24,8 +24,16 @@ def load_neos(neo_csv_path):
|
||||
:param neo_csv_path: A path to a CSV file containing data about near-Earth objects.
|
||||
:return: A collection of `NearEarthObject`s.
|
||||
"""
|
||||
# TODO: Load NEO data from the given CSV file.
|
||||
return ()
|
||||
neos = []
|
||||
with open(neo_csv_path, "r") as csv_file:
|
||||
reader = csv.reader(csv_file)
|
||||
next(reader)
|
||||
for row in reader:
|
||||
neo = NearEarthObject(
|
||||
designation=row[3], name=row[4], diameter=row[15], hazardous=row[7]
|
||||
)
|
||||
neos.append(neo)
|
||||
return neos
|
||||
|
||||
|
||||
def load_approaches(cad_json_path):
|
||||
@@ -34,5 +42,17 @@ def load_approaches(cad_json_path):
|
||||
:param cad_json_path: A path to a JSON file containing data about close approaches.
|
||||
:return: A collection of `CloseApproach`es.
|
||||
"""
|
||||
# TODO: Load close approach data from the given JSON file.
|
||||
return ()
|
||||
approaches = []
|
||||
with open(cad_json_path, "r") as json_file:
|
||||
cad = json.load(json_file)
|
||||
for item in cad["data"]:
|
||||
# 0: pdes, 3: cd, 4: dist, 7: v_rel
|
||||
approach = CloseApproach(
|
||||
designation=item[0],
|
||||
time=item[3],
|
||||
distance=float(item[4]),
|
||||
velocity=float(item[7]),
|
||||
)
|
||||
|
||||
approaches.append(approach)
|
||||
return approaches
|
||||
|
||||
@@ -16,7 +16,9 @@ iterator.
|
||||
|
||||
You'll edit this file in Tasks 3a and 3c.
|
||||
"""
|
||||
|
||||
import operator
|
||||
import itertools
|
||||
|
||||
|
||||
class UnsupportedCriterionError(NotImplementedError):
|
||||
@@ -38,6 +40,7 @@ class AttributeFilter:
|
||||
Concrete subclasses can override the `get` classmethod to provide custom
|
||||
behavior to fetch a desired attribute from the given `CloseApproach`.
|
||||
"""
|
||||
|
||||
def __init__(self, op, value):
|
||||
"""Construct a new `AttributeFilter` from an binary predicate and a reference value.
|
||||
|
||||
@@ -72,12 +75,57 @@ class AttributeFilter:
|
||||
return f"{self.__class__.__name__}(op=operator.{self.op.__name__}, value={self.value})"
|
||||
|
||||
|
||||
class DistanceFilter(AttributeFilter):
|
||||
"""Subclass of AttributeFilter to filter by distance attribute."""
|
||||
|
||||
@classmethod
|
||||
def get(cls, approach):
|
||||
return approach.distance
|
||||
|
||||
|
||||
class VelocityFilter(AttributeFilter):
|
||||
"""Subclass of AttributeFilter to filter by velocity attribute."""
|
||||
|
||||
@classmethod
|
||||
def get(cls, approach):
|
||||
return approach.velocity
|
||||
|
||||
|
||||
class DateFilter(AttributeFilter):
|
||||
"""Subclass of AttributeFilter to filter by date attribute."""
|
||||
|
||||
@classmethod
|
||||
def get(cls, approach):
|
||||
return approach.time.date()
|
||||
|
||||
|
||||
class DiameterFilter(AttributeFilter):
|
||||
"""Subclass of AttributeFilter to filter by diameter attribute."""
|
||||
|
||||
@classmethod
|
||||
def get(cls, approach):
|
||||
return approach.neo.diameter
|
||||
|
||||
|
||||
class HazardousFilter(AttributeFilter):
|
||||
"""Subclass of AttributeFilter to filter by hazardous attribute."""
|
||||
|
||||
@classmethod
|
||||
def get(cls, approach):
|
||||
return approach.neo.hazardous
|
||||
|
||||
|
||||
def create_filters(
|
||||
date=None, start_date=None, end_date=None,
|
||||
distance_min=None, distance_max=None,
|
||||
velocity_min=None, velocity_max=None,
|
||||
diameter_min=None, diameter_max=None,
|
||||
hazardous=None
|
||||
date=None,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
distance_min=None,
|
||||
distance_max=None,
|
||||
velocity_min=None,
|
||||
velocity_max=None,
|
||||
diameter_min=None,
|
||||
diameter_max=None,
|
||||
hazardous=None,
|
||||
):
|
||||
"""Create a collection of filters from user-specified criteria.
|
||||
|
||||
@@ -108,8 +156,30 @@ def create_filters(
|
||||
:param hazardous: Whether the NEO of a matching `CloseApproach` is potentially hazardous.
|
||||
:return: A collection of filters for use with `query`.
|
||||
"""
|
||||
# TODO: Decide how you will represent your filters.
|
||||
return ()
|
||||
filters = []
|
||||
|
||||
if date:
|
||||
filters.append(DateFilter(operator.eq, date))
|
||||
if start_date:
|
||||
filters.append(DateFilter(operator.ge, start_date))
|
||||
if end_date:
|
||||
filters.append(DateFilter(operator.le, end_date))
|
||||
if distance_min:
|
||||
filters.append(DistanceFilter(operator.ge, distance_min))
|
||||
if distance_max:
|
||||
filters.append(DistanceFilter(operator.le, distance_max))
|
||||
if velocity_min:
|
||||
filters.append(VelocityFilter(operator.ge, velocity_min))
|
||||
if velocity_max:
|
||||
filters.append(VelocityFilter(operator.le, velocity_max))
|
||||
if diameter_min:
|
||||
filters.append(DiameterFilter(operator.ge, diameter_min))
|
||||
if diameter_max:
|
||||
filters.append(DiameterFilter(operator.le, diameter_max))
|
||||
if hazardous is not None:
|
||||
filters.append(HazardousFilter(operator.eq, hazardous))
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def limit(iterator, n=None):
|
||||
@@ -121,5 +191,4 @@ def limit(iterator, n=None):
|
||||
:param n: The maximum number of values to produce.
|
||||
:yield: The first (at most) `n` values from the iterator.
|
||||
"""
|
||||
# TODO: Produce at most `n` values from the given iterator.
|
||||
return iterator
|
||||
return itertools.islice(iterator, n) if n else iterator
|
||||
|
||||
@@ -17,7 +17,10 @@ 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
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class NearEarthObject:
|
||||
@@ -32,22 +35,20 @@ class NearEarthObject:
|
||||
initialized to an empty collection, but eventually populated in the
|
||||
`NEODatabase` constructor.
|
||||
"""
|
||||
# TODO: How can you, and should you, change the arguments to this constructor?
|
||||
# If you make changes, be sure to update the comments in this file.
|
||||
|
||||
def __init__(self, **info):
|
||||
"""Create a new `NearEarthObject`.
|
||||
|
||||
:param info: A dictionary of excess keyword arguments supplied to the constructor.
|
||||
"""
|
||||
# TODO: Assign information from the arguments passed to the constructor
|
||||
# onto attributes named `designation`, `name`, `diameter`, and `hazardous`.
|
||||
# You should coerce these values to their appropriate data type and
|
||||
# handle any edge cases, such as a empty name being represented by `None`
|
||||
# and a missing diameter being represented by `float('nan')`.
|
||||
self.designation = ''
|
||||
self.name = None
|
||||
self.diameter = float('nan')
|
||||
self.hazardous = False
|
||||
self.designation = info.get("designation")
|
||||
self.name = None if info.get("name") == "" else info.get("name")
|
||||
self.diameter = (
|
||||
float("nan")
|
||||
if info.get("diameter") in (None, "")
|
||||
else float(info.get("diameter"))
|
||||
)
|
||||
self.hazardous = info.get("hazardous", "N") == "Y"
|
||||
|
||||
# Create an empty initial collection of linked approaches.
|
||||
self.approaches = []
|
||||
@@ -55,20 +56,36 @@ class NearEarthObject:
|
||||
@property
|
||||
def fullname(self):
|
||||
"""Return a representation of the full name of this NEO."""
|
||||
# TODO: Use self.designation and self.name to build a fullname for this object.
|
||||
return ''
|
||||
if self.name:
|
||||
return f"{self.designation} {self.name}"
|
||||
return f"{self.designation}"
|
||||
|
||||
def __str__(self):
|
||||
"""Return `str(self)`."""
|
||||
# TODO: Use this object's attributes to return a human-readable string representation.
|
||||
# The project instructions include one possibility. Peek at the __repr__
|
||||
# method for examples of advanced string formatting.
|
||||
return f"A NearEarthObject ..."
|
||||
hazardous_str = (
|
||||
"is potentially hazardous"
|
||||
if self.hazardous
|
||||
else "is not potentially hazardous"
|
||||
)
|
||||
if not math.isnan(self.diameter):
|
||||
return f"NEO {self.fullname} has a diameter of {self.diameter:.3f} and {hazardous_str}."
|
||||
return f"NEO {self.fullname} has an unknown diameter and {hazardous_str}."
|
||||
|
||||
def __repr__(self):
|
||||
"""Return `repr(self)`, a computer-readable string representation of this object."""
|
||||
return f"NearEarthObject(designation={self.designation!r}, name={self.name!r}, " \
|
||||
f"diameter={self.diameter:.3f}, hazardous={self.hazardous!r})"
|
||||
return (
|
||||
f"NearEarthObject(designation={self.designation!r}, name={self.name!r}, "
|
||||
f"diameter={self.diameter:.3f}, hazardous={self.hazardous!r})"
|
||||
)
|
||||
|
||||
def serialize(self):
|
||||
"""Serialize this `NearEarthObject` into a dictionary."""
|
||||
return {
|
||||
"designation": self.designation,
|
||||
"name": self.name,
|
||||
"diameter_km": self.diameter,
|
||||
"potentially_hazardous": self.hazardous,
|
||||
}
|
||||
|
||||
|
||||
class CloseApproach:
|
||||
@@ -84,21 +101,20 @@ class CloseApproach:
|
||||
private attribute, but the referenced NEO is eventually replaced in the
|
||||
`NEODatabase` constructor.
|
||||
"""
|
||||
# TODO: How can you, and should you, change the arguments to this constructor?
|
||||
# If you make changes, be sure to update the comments in this file.
|
||||
|
||||
def __init__(self, **info):
|
||||
"""Create a new `CloseApproach`.
|
||||
|
||||
:param info: A dictionary of excess keyword arguments supplied to the constructor.
|
||||
"""
|
||||
# TODO: Assign information from the arguments passed to the constructor
|
||||
# onto attributes named `_designation`, `time`, `distance`, and `velocity`.
|
||||
# You should coerce these values to their appropriate data type and handle any edge cases.
|
||||
# The `cd_to_datetime` function will be useful.
|
||||
self._designation = ''
|
||||
self.time = None # TODO: Use the cd_to_datetime function for this attribute.
|
||||
self.distance = 0.0
|
||||
self.velocity = 0.0
|
||||
self._designation = info.get("designation")
|
||||
|
||||
self.time = info.get("time")
|
||||
if self.time:
|
||||
self.time = cd_to_datetime(info.get("time"))
|
||||
|
||||
self.distance = info.get("distance", float("nan"))
|
||||
self.velocity = info.get("velocity", float("nan"))
|
||||
|
||||
# Create an attribute for the referenced NEO, originally None.
|
||||
self.neo = None
|
||||
@@ -116,19 +132,23 @@ class CloseApproach:
|
||||
formatted string that can be used in human-readable representations and
|
||||
in serialization to CSV and JSON files.
|
||||
"""
|
||||
# TODO: Use this object's `.time` attribute and the `datetime_to_str` function to
|
||||
# build a formatted representation of the approach time.
|
||||
# TODO: Use self.designation and self.name to build a fullname for this object.
|
||||
return ''
|
||||
return f"{datetime_to_str(self.time)}"
|
||||
|
||||
def __str__(self):
|
||||
"""Return `str(self)`."""
|
||||
# TODO: Use this object's attributes to return a human-readable string representation.
|
||||
# The project instructions include one possibility. Peek at the __repr__
|
||||
# method for examples of advanced string formatting.
|
||||
return f"A CloseApproach ..."
|
||||
return f"On {self.time_str}, {self.neo.fullname} approaches Earth at a distance of {self.distance:.2f} au and a 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}, distance={self.distance:.2f}, " \
|
||||
f"velocity={self.velocity:.2f}, neo={self.neo!r})"
|
||||
return (
|
||||
f"CloseApproach(time={self.time_str!r}, distance={self.distance:.2f}, "
|
||||
f"velocity={self.velocity:.2f}, neo={self.neo!r})"
|
||||
)
|
||||
|
||||
def serialize(self):
|
||||
"""Serialize this `CloseApproach` into a dictionary."""
|
||||
return {
|
||||
"datetime_utc": self.time_str,
|
||||
"distance_au": self.distance,
|
||||
"velocity_km_s": self.velocity,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ extension determines which of these functions is used.
|
||||
|
||||
You'll edit this file in Part 4.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import json
|
||||
|
||||
@@ -25,10 +26,24 @@ def write_to_csv(results, filename):
|
||||
:param filename: A Path-like object pointing to where the data should be saved.
|
||||
"""
|
||||
fieldnames = (
|
||||
'datetime_utc', 'distance_au', 'velocity_km_s',
|
||||
'designation', 'name', 'diameter_km', 'potentially_hazardous'
|
||||
"datetime_utc",
|
||||
"distance_au",
|
||||
"velocity_km_s",
|
||||
"designation",
|
||||
"name",
|
||||
"diameter_km",
|
||||
"potentially_hazardous",
|
||||
)
|
||||
# TODO: Write the results to a CSV file, following the specification in the instructions.
|
||||
with open(filename, "w") as csv_file:
|
||||
writer = csv.DictWriter(csv_file, fieldnames)
|
||||
writer.writeheader()
|
||||
for result in results:
|
||||
row = {**result.serialize(), **result.neo.serialize()}
|
||||
row["name"] = "" if row["name"] is None else row["name"]
|
||||
row["diameter_km"] = (
|
||||
"" if row["diameter_km"] is float("nan") else row["diameter_km"]
|
||||
)
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
def write_to_json(results, filename):
|
||||
@@ -42,4 +57,25 @@ def write_to_json(results, filename):
|
||||
:param results: An iterable of `CloseApproach` objects.
|
||||
:param filename: A Path-like object pointing to where the data should be saved.
|
||||
"""
|
||||
# TODO: Write the results to a JSON file, following the specification in the instructions.
|
||||
data = []
|
||||
for result in results:
|
||||
item = {**result.serialize(), **result.neo.serialize()}
|
||||
item["name"] = "" if item["name"] is None else item["name"]
|
||||
item["diameter_km"] = (
|
||||
"" if item["diameter_km"] is float("nan") else item["diameter_km"]
|
||||
)
|
||||
data.append(
|
||||
{
|
||||
"datetime_utc": item["datetime_utc"],
|
||||
"distance_au": item["distance_au"],
|
||||
"velocity_km_s": item["velocity_km_s"],
|
||||
"neo": {
|
||||
"designation": item["designation"],
|
||||
"name": item["name"],
|
||||
"diameter_km": item["diameter_km"],
|
||||
"potentially_hazardous": item["potentially_hazardous"],
|
||||
},
|
||||
}
|
||||
)
|
||||
with open(filename, "w") as json_file:
|
||||
json.dump(data, json_file, indent=2)
|
||||
|
||||
Reference in New Issue
Block a user