feder 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
feder-0.2.2/.gitignore ADDED
@@ -0,0 +1,132 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ /.dir-locals.el
132
+ /config*.toml
feder-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: feder
3
+ Version: 0.2.2
4
+ Summary: Feder client API
5
+ Author-email: Ian Ross <iross@mit.edu>
6
+ Requires-Python: >=3.12
feder-0.2.2/README.md ADDED
File without changes
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "feder"
3
+ version = "0.2.2"
4
+ description = "Feder client API"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Ian Ross", email = "iross@mit.edu" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = []
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+ [tool.hatch.build.targets.wheel]
17
+ packages = ["src/feder"]
@@ -0,0 +1,124 @@
1
+ """# Feder flight data collection system
2
+
3
+ Feder is a system for collecting flight data from a range of sources into a
4
+ consolidated database for applications that need easy access to historical
5
+ flight data for statistical or flight attribution purposes. It comes in two
6
+ main parts:
7
+
8
+ 1. An API (published as a Python package) for querying the collected data.
9
+ This data is stored in SQLite3 database files, and the API provides a
10
+ simple interface for making efficient queries to these database files.
11
+
12
+ 2. A set of server processes that collaborate to collect and integrate data
13
+ from different flight data sources, plus infrastructure to deploy these
14
+ server processes so that they work correctly together.
15
+
16
+ **The name**: *Feder* is the German word for "feather" and is also supposed to
17
+ give the feeling of **F**light **D**ata **R**ecorder. It's short and unique
18
+ and easy to say ("fay-der", more or less).
19
+
20
+ ## Concepts: positions vs. trajectories
21
+
22
+ Flight data is usually provided by data sources like FlightAware's Firehose or
23
+ the OpenSky network as individual flight *state vectors*, encoding a single
24
+ snapshot in time of a flight's position. These state vectors are not *exactly*
25
+ individual ADS-B position fixes, because most data provides filter the raw
26
+ ADS-B fixes to reduce data volumes, but they do represent individual position
27
+ fixes at a single point in time. For most applications, it is more useful to
28
+ provide access to *flight trajectories*, i.e. all the position fixes for a
29
+ given flight (possibly restricted to a selected temporal or spatial domain).
30
+
31
+ The Feder API works in terms of these trajectories (or parts of trajectories),
32
+ and the server processes generate trajectory records for full flights by
33
+ collecting position fixes until it appears that a flight is complete and then
34
+ saving a full trajectory record for the flight. As well as being a more natural
35
+ way to think about this data for most applications, this also makes storing and
36
+ querying the flight data much more efficient than a solution that deals only
37
+ with individual position fixes. Applications that need to work with exactly
38
+ those waypoints in a trajectory that lie within a given temporal or spatial
39
+ range may use the waypoint filtering option of the `feder.FlightQuery` class.
40
+
41
+ ## Data units
42
+
43
+ All data values are reported from Feder exactly as they come from the flight
44
+ data providers. [Experiments have
45
+ shown](https://github.mit.edu/iross/flight-data-project/blob/main/experiments/flight-data-comparison/flight-data-comparison.pdf)
46
+ that the different data providers (at least FlightAware and the Contrails API)
47
+ provide comparable data, in the sense that the data is exactly what is sent by
48
+ the aircraft's ADS-B transponder. (FlightAware does some data cleaning using
49
+ other sources, but they make the result look as though it came from the ADS-B
50
+ transponder, just with any anomalous values being cleaned up.)
51
+
52
+ For the user of data from Feder, what this means is:
53
+
54
+ - Latitude and longitude values are "normal" WGS-84 latitudes and longitudes;
55
+
56
+ - The `alt` field of `feder.Point` contains uncorrected [pressure
57
+ altitude](https://en.wikipedia.org/wiki/Pressure_altitude) in feet
58
+ (relative to 1013 hPa) as reported in ADS-B messages;
59
+
60
+ - The `alt_gnss` field of `feder.Point` contains GNSS height in feet relative
61
+ to the WGS-84 datum.
62
+
63
+
64
+ ## API quickstart
65
+
66
+ There is a detailed tutorial for the API [here](feder/tutorial.html), but to
67
+ get started quickly and check that things are working, you can do this in the
68
+ shell:
69
+
70
+ ``` shell
71
+ pip install --extra-index-url https://www.mit.edu/~iross/pypi feder
72
+ export FEDER_DATA_DIR=/home/mcast/data/feder
73
+ ```
74
+
75
+ and then this in Python:
76
+
77
+ ``` python
78
+ from datetime import datetime, timedelta
79
+ from feder import FlightQuery
80
+ t1 = datetime(2025, 5, 22, 20, 0)
81
+ t2 = t1 + timedelta(minutes=30)
82
+ flights = list(FlightQuery(t1, t2).with_orig('KBOS').run())
83
+
84
+ ```
85
+
86
+ This will return a Python array of 10
87
+ [`Trajectory`](doc/api-reference.md#Trajectory) objects of flights crossing
88
+ the given latitude/longitude bounding box in the one hour window starting at
89
+ the given time (all times in UTC).
90
+
91
+ The API efficiently supports a range of query options and data return formats.
92
+ See the [tutorial](feder/tutorial.html) or the API reference documentation
93
+ below for details.
94
+
95
+ ## API documentation
96
+
97
+ """
98
+
99
+ from .query import get_flights, FlightQuery # noqa
100
+ from .available import available_days, available_times, available_sources # noqa
101
+ from .common.models import DataSource, Point, Trajectory # noqa
102
+ from .common.db import BoundingBox, TemporalQueryType, SpatialQueryType # noqa
103
+ from .common.version import get_feder_version # noqa
104
+
105
+ # The following is needed just to build the documentation.
106
+ import feder.tutorial # noqa
107
+
108
+ __all__ = [
109
+ 'get_flights',
110
+ 'FlightQuery',
111
+ 'available_days',
112
+ 'available_times',
113
+ 'available_sources',
114
+ 'DataSource',
115
+ 'Point',
116
+ 'Trajectory',
117
+ 'BoundingBox',
118
+ 'TemporalQueryType',
119
+ 'SpatialQueryType',
120
+ 'get_feder_version',
121
+ 'tutorial'
122
+ ]
123
+
124
+ __version__ = '0.2.2'
@@ -0,0 +1,87 @@
1
+ from datetime import date, datetime, timezone
2
+ import glob
3
+ import itertools
4
+ from operator import itemgetter
5
+ import os
6
+
7
+ from feder.common import DB, DataSource
8
+
9
+
10
+ def available_days() -> list[tuple[date, date]]:
11
+ """Get a list of available days in the data directory."""
12
+ data_dir = os.environ.get('FEDER_DATA_DIR')
13
+ if data_dir is None:
14
+ raise ValueError('environment variable FEDER_DATA_DIR must be set')
15
+
16
+ days = sorted([
17
+ datetime.strptime(os.path.basename(p)[:8], '%Y-%j').date().toordinal()
18
+ for p in glob.iglob(os.path.join(data_dir, '????/*.sqlite'))
19
+ ])
20
+ ranges = []
21
+ for _, g in itertools.groupby(enumerate(days), lambda x: x[0] - x[1]):
22
+ g = list(g)
23
+ ranges.append((date.fromordinal(g[0][1]), date.fromordinal(g[-1][1])))
24
+ return ranges
25
+
26
+
27
+ def available_sources(day: date) -> set[DataSource]:
28
+ """Return data sources in use on a given day."""
29
+ data_dir = os.environ.get('FEDER_DATA_DIR')
30
+ if data_dir is None:
31
+ raise ValueError('environment variable FEDER_DATA_DIR must be set')
32
+
33
+ # Get all data sources from the database.
34
+ return DB(data_dir, day).data_sources()
35
+
36
+
37
+ def available_times(day: date) -> list[tuple[datetime, datetime]]:
38
+ """Get a list of available times for a given day."""
39
+ data_dir = os.environ.get('FEDER_DATA_DIR')
40
+ if data_dir is None:
41
+ raise ValueError('environment variable FEDER_DATA_DIR must be set')
42
+
43
+ # Get all min, max timestamps pairs from the database.
44
+ try:
45
+ timestamp_ranges = DB(data_dir, day).timestamp_ranges()
46
+ except FileNotFoundError:
47
+ return []
48
+
49
+ # Union the timestamp ranges.
50
+ minval = int(datetime.combine(day, datetime.min.time(), tzinfo=timezone.utc).timestamp())
51
+ maxval = int(datetime.combine(day, datetime.max.time(), tzinfo=timezone.utc).timestamp())
52
+ merged = union_of_ranges([clamp_range(r, minval, maxval) for r in timestamp_ranges])
53
+
54
+ # Convert to datetime values and clip to the given day.
55
+ return [
56
+ (datetime.fromtimestamp(r[0], tz=timezone.utc),
57
+ datetime.fromtimestamp(r[1], tz=timezone.utc))
58
+ for r in merged
59
+ ]
60
+
61
+
62
+ def clamp_range(r: tuple[int, int], minval: int, maxval: int) -> tuple[int, int]:
63
+ """Clamp a range to the given min and max values."""
64
+ return (min(maxval, max(minval, r[0])), min(maxval, max(minval, r[1])))
65
+
66
+
67
+ def union_of_ranges(inp: list[tuple[int, int]]) -> list[tuple[int, int]]:
68
+ """Union a list of timestamp ranges."""
69
+ if len(inp) == 0:
70
+ return []
71
+
72
+ # Sort ranges by start timestamp.
73
+ inp.sort(key=itemgetter(0))
74
+
75
+ # Merge overlapping or contiguous ranges.
76
+ merged = []
77
+ current_start, current_end = inp[0]
78
+
79
+ for start, end in inp[1:]:
80
+ if start <= current_end: # Overlapping or contiguous
81
+ current_end = max(current_end, end)
82
+ else:
83
+ merged.append((current_start, current_end))
84
+ current_start, current_end = start, end
85
+
86
+ merged.append((current_start, current_end))
87
+ return merged
@@ -0,0 +1,13 @@
1
+ """Code shared between the Feder public API and backend.
2
+
3
+ The parts of this module relevant to the public API are:
4
+
5
+ - `feder_common.models`: Model classes for trajectories and trajectory points.
6
+ - `feder_common.db`: Database access code.
7
+ """
8
+ from .models import * # noqa
9
+ from .db import * # noqa
10
+ from .utils import * # noqa
11
+ from .version import * # noqa
12
+
13
+ __version__ = '0.2.2'