Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 75 additions & 65 deletions database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,89 +10,99 @@
`extract.load_approaches`.

You'll edit this file in Tasks 2 and 3.
"""


class NEODatabase:
"""A database of near-Earth objects and their close approaches.

A `NEODatabase` contains a collection of NEOs and a collection of close
approaches. It additionally maintains a few auxiliary data structures to
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`.

As a precondition, this constructor assumes that the collections of NEOs
and close approaches haven't yet been linked - that is, the
`.approaches` attribute of each `NearEarthObject` resolves to an empty
collection, and the `.neo` attribute of each `CloseApproach` is None.

However, each `CloseApproach` has an attribute (`._designation`) that
matches the `.designation` attribute of the corresponding NEO. This
constructor modifies the supplied NEOs and close approaches to link them
together - after it's done, the `.approaches` attribute of each NEO has
a collection of that NEO's close approaches, and the `.neo` attribute of
each close approach references the appropriate NEO.

:param neos: A collection of `NearEarthObject`s.
:param approaches: A collection of `CloseApproach`es.
"""
self._neos = neos
self._approaches = approaches
----------------------------------------------------------------------------

# TODO: What additional auxiliary data structures will be useful?
A database encapsulating collections of near-Earth objects and their close approaches.

# TODO: Link together the NEOs and their close approaches.
NEODatabase links NEOs with their close approaches and provides fast lookups
by designation or name, plus a streaming query that yields matching approaches.
"""

def get_neo_by_designation(self, designation):
"""Find and return an NEO by its primary designation.
from __future__ import annotations

If no match is found, return `None` instead.
from typing import Dict, Iterable, Iterator, List, Optional, Sequence

Each NEO in the data set has a unique primary designation, as a string.
from models import NearEarthObject, CloseApproach

The matching is exact - check for spelling and capitalization if no
match is found.

:param designation: The primary designation of the NEO to search for.
:return: The `NearEarthObject` with the desired primary designation, or `None`.
class NEODatabase:
"""A database of near-Earth objects and their close approaches."""

def __init__(self, neos: Sequence[NearEarthObject], approaches: Sequence[CloseApproach]) -> None:
"""Create a new NEODatabase and link NEOs with their close approaches.

Parameters
----------
neos : Sequence[NearEarthObject]
Collection of NEOs (unlinked).
approaches : Sequence[CloseApproach]
Collection of close approaches (unlinked, with '_designation' present).

Notes
-----
After construction:
- Each CloseApproach.neo references its matching NearEarthObject.
- Each NearEarthObject.approaches contains its approaches.
- Auxiliary indices are built for designation and name.
"""
# TODO: Fetch an NEO by its primary designation.
return None
self._neos: List[NearEarthObject] = list(neos)
self._approaches: List[CloseApproach] = list(approaches)

def get_neo_by_name(self, name):
"""Find and return an NEO by its name.
# Auxiliary indices
self._by_des: Dict[str, NearEarthObject] = {neo.designation: neo for neo in self._neos}
self._by_name: Dict[str, NearEarthObject] = {neo.name: neo for neo in self._neos if neo.name}

If no match is found, return `None` instead.
# Link approaches <-> neos
for app in self._approaches:
neo = self._by_des.get(app._designation)
if neo:
app.neo = neo
neo.approaches.append(app)

Not every NEO in the data set has a name. No NEOs are associated with
the empty string nor with the `None` singleton.
def get_neo_by_designation(self, designation: str) -> Optional[NearEarthObject]:
"""Find and return an NEO by its primary designation.

The matching is exact - check for spelling and capitalization if no
match is found.
Parameters
----------
designation : str
The primary designation to search for.

:param name: The name, as a string, of the NEO to search for.
:return: The `NearEarthObject` with the desired name, or `None`.
Returns
-------
Optional[NearEarthObject]
The NEO with the given designation, or None if not found.
"""
# TODO: Fetch an NEO by its name.
return None
return self._by_des.get(designation)

def query(self, filters=()):
"""Query close approaches to generate those that match a collection of filters.
def get_neo_by_name(self, name: str) -> Optional[NearEarthObject]:
"""Find and return an NEO by its name.

Parameters
----------
name : str
The name to search for.

This generates a stream of `CloseApproach` objects that match all of the
provided filters.
Returns
-------
Optional[NearEarthObject]
The NEO with the given name, or None if not found.
"""
return self._by_name.get(name)

If no arguments are provided, generate all known close approaches.
def query(self, filters: Sequence) -> Iterator[CloseApproach]:
"""Yield close approaches that satisfy *all* filters.

The `CloseApproach` objects are generated in internal order, which isn't
guaranteed to be sorted meaningfully, although is often sorted by time.
Parameters
----------
filters : Sequence[AttributeFilter]
A collection of filter predicates.

:param filters: A collection of filters capturing user-specified criteria.
:return: A stream of matching `CloseApproach` objects.
Yields
------
CloseApproach
Matching approaches, lazily (no precomputation of full result set).
"""
# TODO: Generate `CloseApproach` objects that match all of the filters.
for approach in self._approaches:
yield approach
for app in self._approaches:
if all(f(app) for f in filters):
yield app
76 changes: 66 additions & 10 deletions extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,84 @@
line, and uses the resulting collections to build an `NEODatabase`.

You'll edit this file in Task 2.

----------------------------------------------------------------------------

Extract data on near-Earth objects and close approaches from CSV and JSON files.

- load_neos: read NEOs from CSV and return a collection of NearEarthObject.
- load_approaches: read close approaches from JSON and return a collection of CloseApproach.
"""

from __future__ import annotations

import csv
import json
from pathlib import Path
from typing import Iterable, List

from models import NearEarthObject, CloseApproach


def load_neos(neo_csv_path):
def load_neos(neo_csv_path: Path) -> List[NearEarthObject]:
"""Read near-Earth object information from a CSV file.

:param neo_csv_path: A path to a CSV file containing data about near-Earth objects.
:return: A collection of `NearEarthObject`s.
Parameters
----------
neo_csv_path : Path
Path to the CSV file containing near-Earth objects.

Returns
-------
List[NearEarthObject]
A list of NEO instances.
"""
# TODO: Load NEO data from the given CSV file.
return ()
neos: List[NearEarthObject] = []
with open(neo_csv_path, mode="r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
# Keep only the fields we care about; ignore extraneous columns
info = {
"pdes": row.get("pdes"),
"name": row.get("name"),
"diameter": row.get("diameter"),
"pha": row.get("pha"),
}
neos.append(NearEarthObject(**info))
return neos


def load_approaches(cad_json_path):
def load_approaches(cad_json_path: Path) -> List[CloseApproach]:
"""Read close approach data from a JSON file.

:param cad_json_path: A path to a JSON file containing data about close approaches.
:return: A collection of `CloseApproach`es.
The NASA CAD file uses a 'fields' header list and a 'data' body list of rows.
We map indices for 'des', 'cd', 'dist', 'v_rel' and build CloseApproach objects.

Parameters
----------
cad_json_path : Path
Path to the JSON file containing close approach data.

Returns
-------
List[CloseApproach]
A list of CloseApproach instances.
"""
# TODO: Load close approach data from the given JSON file.
return ()
approaches: List[CloseApproach] = []
with open(cad_json_path, mode="r", encoding="utf-8") as f:
raw = json.load(f)

fields = raw.get("fields", [])
data_rows = raw.get("data", [])

idx = {name: i for i, name in enumerate(fields)}
# Expected field names in NASA CAD: 'des', 'cd', 'dist', 'v_rel'
for row in data_rows:
info = {
"des": row[idx.get("des", -1)] if idx.get("des") is not None else None,
"cd": row[idx.get("cd", -1)] if idx.get("cd") is not None else None,
"dist": row[idx.get("dist", -1)] if idx.get("dist") is not None else None,
"v_rel": row[idx.get("v_rel", -1)] if idx.get("v_rel") is not None else None,
}
approaches.append(CloseApproach(**info))
return approaches
Loading