flight-tracer 0.1.0__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.
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
@@ -0,0 +1,2 @@
1
+ include README.md
2
+ include LICENSE
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.2
2
+ Name: flight_tracer
3
+ Version: 0.1.0
4
+ Summary: A package to fetch, process, store and plot aircraft trace data from ADS-B Exchange.
5
+ Home-page: https://github.com/stiles/flight_tracer
6
+ Author: Matt Stiles
7
+ Author-email: mattstiles@gmail.com
8
+ License: CC0-1.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: requests
15
+ Requires-Dist: pandas
16
+ Requires-Dist: geopandas
17
+ Requires-Dist: boto3
18
+ Requires-Dist: matplotlib
19
+ Requires-Dist: contextily
20
+ Requires-Dist: shapely
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # Tracking ADS-B Exchange flights
33
+
34
+ FlightTracer is a Python package to fetch and process aircraft trace data from ADS-B Exchange. It supports a single ICAO code or a list of codes — and it has an option to upload processed data as CSV and GeoJSON to Amazon S3. FlightTracer also detects separate flight legs based on significant time gaps and creates a combined flight leg identifier (call_sign, date, leg) for easier differentiation in GIS tools. In addition, the package also provides plotting capabilities with a basemap and an option to save the plot as a PNG.
35
+
36
+ This project is in the early stages of development. Contributions and feedback welcome.
37
+
38
+ ## Installation
39
+
40
+ 1. Clone the repository or download the source code
41
+ 2. Install the required dependencies using pip
42
+
43
+ ~~~bash
44
+ pip install requests pandas geopandas boto3 contextily matplotlib
45
+ ~~~
46
+
47
+ ## Usage
48
+
49
+ FlightTracer can be used with either a list of aircraft IDs or a metadata URL that contains aircraft information. You can also configure AWS credentials if you wish to upload the output to S3. In addition, you can pass an AWS profile name if you have multiple sets of credentials in your environment.
50
+
51
+ ### Example
52
+
53
+ Below is an example of how to use FlightTracer:
54
+
55
+ ~~~python
56
+ #!/usr/bin/env python
57
+ """
58
+ fetch_example.py
59
+
60
+ This example fetches real flight trace data, processes it (computing the continuous ping_time
61
+ and inferring separate flight legs based on time gaps), saves the results locally
62
+ with a filename that includes the ICAO code(s) and today's date, and optionally
63
+ uploads the CSV and GeoJSON files to S3 using a specified AWS profile.
64
+ It also shows how to generate a plot of the flight activity and save the plot as a PNG.
65
+ """
66
+
67
+ import os
68
+ from datetime import date, datetime
69
+ from flight_tracer import FlightTracer
70
+ import geopandas as gpd
71
+
72
+ # Option 1: Use explicit AWS credentials (via environment variables or directly)
73
+ aws_credentials = {
74
+ "aws_access_key_id": os.getenv("MY_AWS_ACCESS_KEY_ID"),
75
+ "aws_secret_access_key": os.getenv("MY_AWS_SECRET_ACCESS_KEY")
76
+ }
77
+
78
+ # Option 2: Alternatively, use an AWS profile from your environment
79
+ aws_profile = os.getenv("MY_AWS_PROFILE") # e.g., "my_profile_name"
80
+
81
+ # Initialize flight_tracer with either explicit credentials or an AWS profile.
82
+ # For example, to use explicit credentials:
83
+ # tracer = flight_tracer(aircraft_ids=["a97753"], aws_creds=aws_credentials)
84
+ # Or, to use an AWS profile:
85
+ tracer = FlightTracer(aircraft_ids=["a97753"], aws_profile=aws_profile)
86
+
87
+ # Define the date range for trace data
88
+ start = date(2025, 1, 28)
89
+ end = date(2025, 2, 1)
90
+
91
+ # Fetch raw flight trace data from ADS-B Exchange
92
+ print("Fetching raw flight trace data...")
93
+ raw_df = tracer.get_traces(start, end)
94
+ if raw_df.empty:
95
+ print("No valid data to process.")
96
+ else:
97
+ print("Raw data sample:")
98
+ print(raw_df.head())
99
+
100
+ # Process trace data into a GeoDataFrame (computing continuous ping_time in UTC
101
+ # and detecting flight leg changes based on time gaps). A new 'flight_leg' column is
102
+ # created that combines call_sign, flight_date, and leg_id.
103
+ print("\nProcessing flight data into a GeoDataFrame...")
104
+ gdf = tracer.process_flight_data(raw_df)
105
+
106
+ # Optionally, retain ground points:
107
+ # gdf = tracer.process_flight_data(raw_df, filter_ground=False)
108
+
109
+ print("Processed GeoDataFrame sample:")
110
+ print(gdf.head())
111
+
112
+ # Optionally, inspect the unique flight legs detected in the data.
113
+ print("\nUnique flight legs detected:")
114
+ print(gdf["flight_leg"].unique())
115
+
116
+ # Build dynamic filenames that include the ICAO code(s) and today's date
117
+ icao_str = "_".join(tracer.aircraft_ids)
118
+ date_str = datetime.today().strftime("%Y%m%d")
119
+ csv_filename = f"data/flight_traces_{icao_str}_{date_str}.csv"
120
+ geojson_filename = f"data/flight_traces_{icao_str}_{date_str}.geojson"
121
+
122
+ # Save the processed data locally as CSV and GeoJSON
123
+ gdf.to_csv(csv_filename, index=False)
124
+ gdf.to_file(geojson_filename, driver="GeoJSON")
125
+ print(f"\nSaved processed data locally as '{csv_filename}' and '{geojson_filename}'.")
126
+
127
+ # Export linestring geometry for each flight leg
128
+ linestring_geojson_filename = f"data/flight_traces_lines_{icao_str}_{date_str}.geojson"
129
+ tracer.export_linestring_geojson(gdf, linestring_geojson_filename)
130
+
131
+ # Optionally upload the processed files to S3
132
+ bucket_name = "your-bucket-name" # replace with your bucket name
133
+ csv_object_name = f"flight_tracer/flight_traces_{icao_str}_{date_str}.csv"
134
+ geojson_object_name = f"flight_tracer/flight_traces_{icao_str}_{date_str}.geojson"
135
+ print("\nUploading files to S3 (if AWS credentials or profile are configured)...")
136
+ tracer.upload_to_s3(gdf, bucket_name, csv_object_name, geojson_object_name)
137
+ print("Upload process completed.")
138
+
139
+ # Plot the points with a basemap and optionally save the plot as a PNG
140
+ fig_filename = f"visuals/flight_map_{icao_str}_{date_str}.png"
141
+ tracer.plot_flights(gdf, geometry_type='points', figsize=(12,10), fig_filename=fig_filename)
142
+ ~~~
143
+
144
+ ## Configuration
145
+
146
+ The package supports the following configurations:
147
+
148
+ - **Aircraft IDs or metadata URL**: Provide either a list of ICAO codes or a metadata URL to extract aircraft information.
149
+ - **AWS credentials or profile**: Pass AWS credentials as a dictionary, specify an AWS profile, or set them as environment variables.
150
+ - **Date range**: Define the start and end dates to fetch trace data.
151
+
152
+ ## Notes
153
+
154
+ - Ensure that your AWS credentials or profile are configured correctly if you wish to use the S3 upload feature.
155
+ - The package fetches data from ADS-B Exchange so the availability of data depends on the public API.
156
+ - Flight leg detection is based on a configurable time gap threshold (default is 15 minutes). Adjust as needed for your data.
157
+ - Plotting functionality includes an option to expand the plotted extent (via `pad_factor`) for broader context and to save the plot as a PNG.
158
+
159
+ ## Roadmap for Enhancements
160
+
161
+ #### Enrich with external metadata:
162
+ - Build tools to “hydrate” your flight data with additional aircraft metadata from external sources such as FAA, ICAO, or other databases (e.g., aircraft type, operator, age, etc.).
163
+
164
+ #### Package distribution:
165
+ - Finalize the code structure (including tests, documentation, and a setup script) and publish the package to PyPI for installation via pip.
166
+
167
+ #### Enhanced documentation and examples:
168
+ - Update the README with comprehensive, end-to-end examples for various scenarios (e.g., pulling all flights for a month of a particular aircraft, processing a list of aircraft from a metadata URL, etc.), including usage examples for linestring export, leg splitting, and metadata enrichment.
169
+
170
+ #### Configurable thresholds and options:
171
+ - Allow users to adjust parameters like the time-gap threshold for determining new flight legs, output formats, and which metadata fields to hydrate.
172
+
173
+ #### Performance and error handling improvements:
174
+ - Add caching or parallel processing options to improve performance when fetching data over a long date range or for multiple aircraft.
175
+ - Improve error handling to gracefully skip problematic dates or flights while logging issues.
176
+
177
+ ## License
178
+
179
+ This project is licensed under the **Creative Commons CC0 1.0 Universal (CC0 1.0) Public Domain Dedication**.
180
+
181
+ This means that to the extent possible under law, the creator has waived all copyright and related or neighboring rights to this work.
182
+
183
+ You can copy, modify, distribute, and perform the work, even for commercial purposes, all without asking permission.
184
+
185
+ For more details, refer to the full [CC0 1.0 Universal License](https://creativecommons.org/publicdomain/zero/1.0/legalcode).
186
+
187
+ ![CC0 Badge](https://licensebuttons.net/p/zero/1.0/88x31.png)
@@ -0,0 +1,156 @@
1
+ # Tracking ADS-B Exchange flights
2
+
3
+ FlightTracer is a Python package to fetch and process aircraft trace data from ADS-B Exchange. It supports a single ICAO code or a list of codes — and it has an option to upload processed data as CSV and GeoJSON to Amazon S3. FlightTracer also detects separate flight legs based on significant time gaps and creates a combined flight leg identifier (call_sign, date, leg) for easier differentiation in GIS tools. In addition, the package also provides plotting capabilities with a basemap and an option to save the plot as a PNG.
4
+
5
+ This project is in the early stages of development. Contributions and feedback welcome.
6
+
7
+ ## Installation
8
+
9
+ 1. Clone the repository or download the source code
10
+ 2. Install the required dependencies using pip
11
+
12
+ ~~~bash
13
+ pip install requests pandas geopandas boto3 contextily matplotlib
14
+ ~~~
15
+
16
+ ## Usage
17
+
18
+ FlightTracer can be used with either a list of aircraft IDs or a metadata URL that contains aircraft information. You can also configure AWS credentials if you wish to upload the output to S3. In addition, you can pass an AWS profile name if you have multiple sets of credentials in your environment.
19
+
20
+ ### Example
21
+
22
+ Below is an example of how to use FlightTracer:
23
+
24
+ ~~~python
25
+ #!/usr/bin/env python
26
+ """
27
+ fetch_example.py
28
+
29
+ This example fetches real flight trace data, processes it (computing the continuous ping_time
30
+ and inferring separate flight legs based on time gaps), saves the results locally
31
+ with a filename that includes the ICAO code(s) and today's date, and optionally
32
+ uploads the CSV and GeoJSON files to S3 using a specified AWS profile.
33
+ It also shows how to generate a plot of the flight activity and save the plot as a PNG.
34
+ """
35
+
36
+ import os
37
+ from datetime import date, datetime
38
+ from flight_tracer import FlightTracer
39
+ import geopandas as gpd
40
+
41
+ # Option 1: Use explicit AWS credentials (via environment variables or directly)
42
+ aws_credentials = {
43
+ "aws_access_key_id": os.getenv("MY_AWS_ACCESS_KEY_ID"),
44
+ "aws_secret_access_key": os.getenv("MY_AWS_SECRET_ACCESS_KEY")
45
+ }
46
+
47
+ # Option 2: Alternatively, use an AWS profile from your environment
48
+ aws_profile = os.getenv("MY_AWS_PROFILE") # e.g., "my_profile_name"
49
+
50
+ # Initialize flight_tracer with either explicit credentials or an AWS profile.
51
+ # For example, to use explicit credentials:
52
+ # tracer = flight_tracer(aircraft_ids=["a97753"], aws_creds=aws_credentials)
53
+ # Or, to use an AWS profile:
54
+ tracer = FlightTracer(aircraft_ids=["a97753"], aws_profile=aws_profile)
55
+
56
+ # Define the date range for trace data
57
+ start = date(2025, 1, 28)
58
+ end = date(2025, 2, 1)
59
+
60
+ # Fetch raw flight trace data from ADS-B Exchange
61
+ print("Fetching raw flight trace data...")
62
+ raw_df = tracer.get_traces(start, end)
63
+ if raw_df.empty:
64
+ print("No valid data to process.")
65
+ else:
66
+ print("Raw data sample:")
67
+ print(raw_df.head())
68
+
69
+ # Process trace data into a GeoDataFrame (computing continuous ping_time in UTC
70
+ # and detecting flight leg changes based on time gaps). A new 'flight_leg' column is
71
+ # created that combines call_sign, flight_date, and leg_id.
72
+ print("\nProcessing flight data into a GeoDataFrame...")
73
+ gdf = tracer.process_flight_data(raw_df)
74
+
75
+ # Optionally, retain ground points:
76
+ # gdf = tracer.process_flight_data(raw_df, filter_ground=False)
77
+
78
+ print("Processed GeoDataFrame sample:")
79
+ print(gdf.head())
80
+
81
+ # Optionally, inspect the unique flight legs detected in the data.
82
+ print("\nUnique flight legs detected:")
83
+ print(gdf["flight_leg"].unique())
84
+
85
+ # Build dynamic filenames that include the ICAO code(s) and today's date
86
+ icao_str = "_".join(tracer.aircraft_ids)
87
+ date_str = datetime.today().strftime("%Y%m%d")
88
+ csv_filename = f"data/flight_traces_{icao_str}_{date_str}.csv"
89
+ geojson_filename = f"data/flight_traces_{icao_str}_{date_str}.geojson"
90
+
91
+ # Save the processed data locally as CSV and GeoJSON
92
+ gdf.to_csv(csv_filename, index=False)
93
+ gdf.to_file(geojson_filename, driver="GeoJSON")
94
+ print(f"\nSaved processed data locally as '{csv_filename}' and '{geojson_filename}'.")
95
+
96
+ # Export linestring geometry for each flight leg
97
+ linestring_geojson_filename = f"data/flight_traces_lines_{icao_str}_{date_str}.geojson"
98
+ tracer.export_linestring_geojson(gdf, linestring_geojson_filename)
99
+
100
+ # Optionally upload the processed files to S3
101
+ bucket_name = "your-bucket-name" # replace with your bucket name
102
+ csv_object_name = f"flight_tracer/flight_traces_{icao_str}_{date_str}.csv"
103
+ geojson_object_name = f"flight_tracer/flight_traces_{icao_str}_{date_str}.geojson"
104
+ print("\nUploading files to S3 (if AWS credentials or profile are configured)...")
105
+ tracer.upload_to_s3(gdf, bucket_name, csv_object_name, geojson_object_name)
106
+ print("Upload process completed.")
107
+
108
+ # Plot the points with a basemap and optionally save the plot as a PNG
109
+ fig_filename = f"visuals/flight_map_{icao_str}_{date_str}.png"
110
+ tracer.plot_flights(gdf, geometry_type='points', figsize=(12,10), fig_filename=fig_filename)
111
+ ~~~
112
+
113
+ ## Configuration
114
+
115
+ The package supports the following configurations:
116
+
117
+ - **Aircraft IDs or metadata URL**: Provide either a list of ICAO codes or a metadata URL to extract aircraft information.
118
+ - **AWS credentials or profile**: Pass AWS credentials as a dictionary, specify an AWS profile, or set them as environment variables.
119
+ - **Date range**: Define the start and end dates to fetch trace data.
120
+
121
+ ## Notes
122
+
123
+ - Ensure that your AWS credentials or profile are configured correctly if you wish to use the S3 upload feature.
124
+ - The package fetches data from ADS-B Exchange so the availability of data depends on the public API.
125
+ - Flight leg detection is based on a configurable time gap threshold (default is 15 minutes). Adjust as needed for your data.
126
+ - Plotting functionality includes an option to expand the plotted extent (via `pad_factor`) for broader context and to save the plot as a PNG.
127
+
128
+ ## Roadmap for Enhancements
129
+
130
+ #### Enrich with external metadata:
131
+ - Build tools to “hydrate” your flight data with additional aircraft metadata from external sources such as FAA, ICAO, or other databases (e.g., aircraft type, operator, age, etc.).
132
+
133
+ #### Package distribution:
134
+ - Finalize the code structure (including tests, documentation, and a setup script) and publish the package to PyPI for installation via pip.
135
+
136
+ #### Enhanced documentation and examples:
137
+ - Update the README with comprehensive, end-to-end examples for various scenarios (e.g., pulling all flights for a month of a particular aircraft, processing a list of aircraft from a metadata URL, etc.), including usage examples for linestring export, leg splitting, and metadata enrichment.
138
+
139
+ #### Configurable thresholds and options:
140
+ - Allow users to adjust parameters like the time-gap threshold for determining new flight legs, output formats, and which metadata fields to hydrate.
141
+
142
+ #### Performance and error handling improvements:
143
+ - Add caching or parallel processing options to improve performance when fetching data over a long date range or for multiple aircraft.
144
+ - Improve error handling to gracefully skip problematic dates or flights while logging issues.
145
+
146
+ ## License
147
+
148
+ This project is licensed under the **Creative Commons CC0 1.0 Universal (CC0 1.0) Public Domain Dedication**.
149
+
150
+ This means that to the extent possible under law, the creator has waived all copyright and related or neighboring rights to this work.
151
+
152
+ You can copy, modify, distribute, and perform the work, even for commercial purposes, all without asking permission.
153
+
154
+ For more details, refer to the full [CC0 1.0 Universal License](https://creativecommons.org/publicdomain/zero/1.0/legalcode).
155
+
156
+ ![CC0 Badge](https://licensebuttons.net/p/zero/1.0/88x31.png)
@@ -0,0 +1 @@
1
+ from .core import FlightTracer
@@ -0,0 +1,303 @@
1
+ # flight_tracer/core.py
2
+ import requests
3
+ import pandas as pd
4
+ import geopandas as gpd
5
+ import boto3
6
+ import os
7
+ from datetime import date, timedelta
8
+ from io import BytesIO
9
+ from shapely.geometry import LineString
10
+ import matplotlib.pyplot as plt
11
+ import contextily as ctx
12
+
13
+ class FlightTracer:
14
+ # Default columns from the ADSB trace data (we’ll drop the extra ones)
15
+ DEFAULT_COLUMNS = [
16
+ "time", "lat", "lon", "altitude", "ground_speed", "heading",
17
+ "unknown1", "baro_rate", "details", "code", "alt_geom",
18
+ "unknown2", "unknown3", "unknown4",
19
+ ]
20
+ DROP_COLUMNS = ["unknown1", "code", "baro_rate", "unknown2", "unknown3", "unknown4"]
21
+
22
+ def __init__(self, aircraft_ids=None, meta_url=None, aws_creds=None, aws_profile=None):
23
+ """
24
+ Initialize with a list of aircraft_ids or a metadata URL.
25
+ Optionally pass aws_creds as a dict with keys:
26
+ 'aws_access_key_id' and 'aws_secret_access_key'.
27
+ Alternatively, pass aws_profile to use a specific AWS CLI profile.
28
+ """
29
+ if meta_url:
30
+ meta_df = pd.read_json(meta_url)
31
+ # Clean up and extract ICAO codes from metadata
32
+ self.aircraft_ids = meta_df["icao"].str.strip().str.lower().tolist()
33
+ self.meta_df = meta_df
34
+ elif aircraft_ids:
35
+ self.aircraft_ids = [ac.strip().lower() for ac in aircraft_ids]
36
+ self.meta_df = None
37
+ else:
38
+ raise ValueError("Either aircraft_ids or meta_url must be provided")
39
+
40
+ # Set up S3 client using aws_profile if provided, else explicit credentials if provided
41
+ if aws_profile:
42
+ session = boto3.Session(profile_name=aws_profile)
43
+ self.s3_client = session.client('s3')
44
+ elif aws_creds:
45
+ self.s3_client = boto3.client(
46
+ 's3',
47
+ aws_access_key_id=aws_creds.get('aws_access_key_id'),
48
+ aws_secret_access_key=aws_creds.get('aws_secret_access_key')
49
+ )
50
+ else:
51
+ self.s3_client = None
52
+
53
+ def generate_urls(self, start_date, end_date):
54
+ """Generate ADSBExchange URLs for each aircraft over a date range."""
55
+ base_url = "https://globe.adsbexchange.com/globe_history/"
56
+ delta = timedelta(days=1)
57
+ urls = []
58
+ for icao in self.aircraft_ids:
59
+ # Use last 2 chars to build the URL path
60
+ icao_suffix = icao[-2:]
61
+ current_date = start_date
62
+ while current_date <= end_date:
63
+ year = current_date.strftime("%Y")
64
+ month = current_date.strftime("%m")
65
+ day = current_date.strftime("%d")
66
+ url = f"{base_url}{year}/{month}/{day}/traces/{icao_suffix}/trace_full_{icao}.json"
67
+ urls.append((url, icao))
68
+ current_date += delta
69
+ return urls
70
+
71
+ def fetch_trace_data(self, url, icao):
72
+ """Fetch and return a trace DataFrame from a given URL."""
73
+ headers = {"Referer": f"https://globe.adsbexchange.com/?icao={icao}"}
74
+ response = requests.get(url, headers=headers)
75
+ if response.status_code == 200:
76
+ data = response.json()
77
+ if data and "trace" in data:
78
+ trace_df = pd.DataFrame(data["trace"], columns=self.DEFAULT_COLUMNS)
79
+ trace_df = trace_df.drop(columns=self.DROP_COLUMNS, errors='ignore')
80
+ # Add additional metadata
81
+ trace_df["nnumber"] = data["r"]
82
+ trace_df["model"] = data["t"]
83
+ trace_df["desc"] = data["desc"]
84
+ # Convert the initial timestamp to a datetime
85
+ trace_df["timestamp"] = pd.to_datetime(data["timestamp"], unit="s")
86
+ # Compute the actual ping time by adding the offset
87
+ trace_df["ping_time"] = trace_df["timestamp"] + pd.to_timedelta(trace_df["time"], unit="s")
88
+ trace_df["icao"] = icao # Add the aircraft id to the DataFrame
89
+ return trace_df
90
+ return None
91
+
92
+
93
+ def get_traces(self, start_date, end_date):
94
+ """
95
+ Loop through generated URLs to fetch trace data.
96
+ Returns a concatenated DataFrame of all valid traces.
97
+ """
98
+ urls = self.generate_urls(start_date, end_date)
99
+ traces = []
100
+ for url, icao in urls:
101
+ trace_df = self.fetch_trace_data(url, icao)
102
+ if trace_df is not None and not trace_df.empty:
103
+ traces.append(trace_df)
104
+ # Print a friendly message using the first timestamp
105
+ ts = pd.to_datetime(trace_df["timestamp"].iloc[0]).strftime('%b %-d, %Y')
106
+ print(f"Yay! {icao} flew on {ts}.")
107
+ else:
108
+ print(f"No data for {icao} on {url}.")
109
+ if traces:
110
+ return pd.concat(traces).reset_index(drop=True).sort_values("timestamp")
111
+ else:
112
+ print("No valid trace data collected.")
113
+ return pd.DataFrame()
114
+
115
+ def process_flight_data(self, df, mapping_info=None, filter_ground=True):
116
+ """
117
+ Process the raw trace data:
118
+ - Compute point_time (UTC) and convert to US/Pacific.
119
+ - Detect flight leg changes by looking at time gaps.
120
+ - Normalize JSON details into columns.
121
+ - Optionally map a flight column to additional info (like owner).
122
+ - Create a combined flight_leg column.
123
+ - Optionally filter out ground-level data.
124
+ - Return a GeoDataFrame.
125
+
126
+ Parameters:
127
+ df (DataFrame): Raw flight trace data.
128
+ mapping_info (tuple or None): If provided, maps flight details to additional metadata.
129
+ filter_ground (bool): If True (default), removes rows where altitude is "ground".
130
+
131
+ Returns:
132
+ GeoDataFrame: Processed flight data with spatial points.
133
+ """
134
+ # First sort and compute the continuous ping time
135
+ df = df.sort_values(['timestamp', 'time'])
136
+ df["point_time"] = df["timestamp"] + pd.to_timedelta(df["time"], unit="s")
137
+ df["timestamp_pst"] = df["point_time"].dt.tz_localize("UTC").dt.tz_convert("US/Pacific")
138
+ df["point_time_pst_clean"] = df["timestamp_pst"].dt.strftime("%H:%M:%S")
139
+ df["flight_date_pst"] = df["timestamp_pst"].dt.strftime("%Y-%m-%d")
140
+
141
+ # --- Begin leg detection ---
142
+ df = df.sort_values(['icao', 'point_time'])
143
+ df['time_diff'] = df.groupby(['icao', 'flight_date_pst'])['point_time'].diff()
144
+ threshold = pd.Timedelta(minutes=15)
145
+ df['new_leg'] = (df['time_diff'] > threshold).fillna(0).astype(int)
146
+ df['leg_id'] = df.groupby(['icao', 'flight_date_pst'])['new_leg'].cumsum() + 1
147
+ # --- End leg detection ---
148
+
149
+ # Extract the call sign from the 'details' column and fill forward missing values
150
+ df["call_sign"] = pd.json_normalize(df['details'])['flight'].str.strip()
151
+ df["call_sign"] = df["call_sign"].ffill()
152
+
153
+ # Create a combined flight_leg field: call_sign + "_" + flight_date_pst + "_leg" + leg_id
154
+ df["flight_leg"] = df["call_sign"] + "_" + df["flight_date_pst"] + "_leg" + df["leg_id"].astype(str)
155
+
156
+ # Flatten the 'details' JSON column (drop alt_geom if present)
157
+ details_df = pd.json_normalize(df["details"]).drop(columns=["alt_geom"], errors='ignore')
158
+ df = df.join(details_df)
159
+
160
+ # Optionally apply a mapping (e.g. flight -> owner) if mapping_info is provided
161
+ if mapping_info and "flight" in df.columns:
162
+ meta_df, key_col, value_col, target_col = mapping_info
163
+ mapping = meta_df.set_index(key_col)[value_col].to_dict()
164
+ df[target_col] = df["flight"].map(mapping)
165
+
166
+ # Select a common set of columns, explicitly including point_time for later sorting.
167
+ cols = ["flight", "point_time", "flight_date_pst", "point_time_pst_clean", "altitude",
168
+ "ground_speed", "heading", "lat", "lon", "icao", "call_sign", "leg_id", "flight_leg"]
169
+ if mapping_info:
170
+ cols.append(target_col)
171
+
172
+ # Apply altitude filter based on user preference
173
+ if filter_ground:
174
+ df = df.query('altitude != "ground"').copy()
175
+
176
+ # Create and return a GeoDataFrame with points from lon/lat
177
+ return gpd.GeoDataFrame(df[cols], geometry=gpd.points_from_xy(df.lon, df.lat))
178
+
179
+
180
+ def export_linestring_geojson(self, gdf, output_file):
181
+ """
182
+ Given a GeoDataFrame of flight trace points (which includes a 'flight_leg'
183
+ and a continuous 'point_time' column), group the data by flight_leg,
184
+ create a LineString for each leg, and export the result as a GeoJSON file.
185
+
186
+ Parameters:
187
+ gdf (GeoDataFrame): The GeoDataFrame of flight points.
188
+ output_file (str): Path to the output GeoJSON file.
189
+ """
190
+ legs = []
191
+ # Group by the unique flight_leg identifier
192
+ for flight_leg, group in gdf.groupby("flight_leg"):
193
+ # Sort the group by point_time so the points are in order
194
+ group = group.sort_values("point_time")
195
+ points = list(group.geometry)
196
+ # If more than one point, create a LineString; otherwise, use the single point.
197
+ if len(points) > 1:
198
+ geometry = LineString(points)
199
+ else:
200
+ geometry = points[0]
201
+ # Capture representative attributes from the first row.
202
+ legs.append({
203
+ "flight_leg": flight_leg,
204
+ "icao": group["icao"].iloc[0],
205
+ "call_sign": group["call_sign"].iloc[0],
206
+ "flight_date_pst": group["flight_date_pst"].iloc[0],
207
+ "leg_id": group["leg_id"].iloc[0],
208
+ "geometry": geometry
209
+ })
210
+
211
+ gdf_lines = gpd.GeoDataFrame(legs, crs=gdf.crs)
212
+ gdf_lines.to_file(output_file, driver="GeoJSON")
213
+ print(f"Linestring GeoJSON exported to {output_file}")
214
+
215
+
216
+
217
+ def plot_flights(self, gdf, geometry_type='points', figsize=(10,10), pad_factor=0.2, zoom=None, fig_filename=None):
218
+ """
219
+ Plot flight activity from a GeoDataFrame with a basemap.
220
+
221
+ Parameters:
222
+ gdf (GeoDataFrame): The GeoDataFrame containing flight trace points or lines.
223
+ geometry_type (str): 'points' or 'lines' – determines what geometry to plot.
224
+ figsize (tuple): Figure size for the plot.
225
+ pad_factor (float): Fraction by which to expand the bounds for additional context.
226
+ zoom (int or None): Optional zoom level override for the basemap.
227
+ fig_filename (str or None): If provided, the plot will be saved to this PNG file.
228
+
229
+ This method reprojects the GeoDataFrame to EPSG:3857, computes a padded extent, plots
230
+ the data with a basemap, and optionally saves the figure.
231
+ """
232
+ # Ensure the GeoDataFrame has a CRS; assume EPSG:4326 if not set.
233
+ if gdf.crs is None:
234
+ gdf = gdf.set_crs(epsg=4326)
235
+ # Reproject to Web Mercator for basemap compatibility.
236
+ gdf_plot = gdf.to_crs(epsg=3857)
237
+
238
+ fig, ax = plt.subplots(figsize=figsize)
239
+
240
+ # Plot based on the desired geometry type.
241
+ if geometry_type == 'points':
242
+ gdf_plot.plot(ax=ax, marker='o', color='#f18851', markersize=5, label='Flight points')
243
+ elif geometry_type == 'lines':
244
+ gdf_plot.plot(ax=ax, linewidth=2, color='#f18851', label='Flight path')
245
+ else:
246
+ raise ValueError("geometry_type must be either 'points' or 'lines'")
247
+
248
+ # Get the total bounds: [xmin, ymin, xmax, ymax]
249
+ xmin, ymin, xmax, ymax = gdf_plot.total_bounds
250
+ # Calculate padding based on the pad_factor.
251
+ x_pad = (xmax - xmin) * pad_factor
252
+ y_pad = (ymax - ymin) * pad_factor
253
+ extent = [xmin - x_pad, ymin - y_pad, xmax + x_pad, ymax + y_pad]
254
+
255
+ # Set the axis limits to the padded extent.
256
+ ax.set_xlim(extent[0], extent[2])
257
+ ax.set_ylim(extent[1], extent[3])
258
+
259
+ # Add a basemap using CartoDB Positron.
260
+ if zoom is not None:
261
+ ctx.add_basemap(ax, source=ctx.providers.CartoDB.Positron, zoom=zoom, reset_extent=False)
262
+ else:
263
+ ctx.add_basemap(ax, source=ctx.providers.CartoDB.Positron, reset_extent=False)
264
+
265
+ # Clean up the plot aesthetics.
266
+ ax.set_axis_off()
267
+ ax.legend()
268
+ plt.tight_layout()
269
+ plt.title("Flight sketch")
270
+
271
+ # If a figure filename is provided, save the figure as a PNG.
272
+ if fig_filename:
273
+ # Create the directory if it doesn't exist.
274
+ os.makedirs(os.path.dirname(fig_filename), exist_ok=True)
275
+ plt.savefig(fig_filename, dpi=300, bbox_inches='tight')
276
+ print(f"Figure saved as {fig_filename}")
277
+
278
+ plt.show()
279
+
280
+
281
+ def upload_to_s3(self, gdf, bucket_name, csv_object_name, geojson_object_name):
282
+ """Upload the GeoDataFrame as both CSV and GeoJSON to S3 (if configured)."""
283
+ if not self.s3_client:
284
+ print("S3 client not configured; skipping upload.")
285
+ return
286
+
287
+ # Save CSV in memory and upload
288
+ csv_buffer = BytesIO()
289
+ gdf.to_csv(csv_buffer, index=False)
290
+ csv_buffer.seek(0)
291
+ self.s3_client.put_object(Bucket=bucket_name, Key=csv_object_name, Body=csv_buffer.getvalue())
292
+ print(f"CSV uploaded to s3://{bucket_name}/{csv_object_name}")
293
+
294
+ # Remove non-serializable columns (e.g., 'point_time') before exporting to GeoJSON.
295
+ # Alternatively, you could convert it to a string using .astype(str)
296
+ gdf_json = gdf.drop(columns=["point_time"], errors="ignore")
297
+ geojson_str = gdf_json.to_json()
298
+ self.s3_client.put_object(
299
+ Bucket=bucket_name,
300
+ Key=geojson_object_name,
301
+ Body=geojson_str.encode('utf-8')
302
+ )
303
+ print(f"GeoJSON uploaded to s3://{bucket_name}/{geojson_object_name}")
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.2
2
+ Name: flight_tracer
3
+ Version: 0.1.0
4
+ Summary: A package to fetch, process, store and plot aircraft trace data from ADS-B Exchange.
5
+ Home-page: https://github.com/stiles/flight_tracer
6
+ Author: Matt Stiles
7
+ Author-email: mattstiles@gmail.com
8
+ License: CC0-1.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: requests
15
+ Requires-Dist: pandas
16
+ Requires-Dist: geopandas
17
+ Requires-Dist: boto3
18
+ Requires-Dist: matplotlib
19
+ Requires-Dist: contextily
20
+ Requires-Dist: shapely
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # Tracking ADS-B Exchange flights
33
+
34
+ FlightTracer is a Python package to fetch and process aircraft trace data from ADS-B Exchange. It supports a single ICAO code or a list of codes — and it has an option to upload processed data as CSV and GeoJSON to Amazon S3. FlightTracer also detects separate flight legs based on significant time gaps and creates a combined flight leg identifier (call_sign, date, leg) for easier differentiation in GIS tools. In addition, the package also provides plotting capabilities with a basemap and an option to save the plot as a PNG.
35
+
36
+ This project is in the early stages of development. Contributions and feedback welcome.
37
+
38
+ ## Installation
39
+
40
+ 1. Clone the repository or download the source code
41
+ 2. Install the required dependencies using pip
42
+
43
+ ~~~bash
44
+ pip install requests pandas geopandas boto3 contextily matplotlib
45
+ ~~~
46
+
47
+ ## Usage
48
+
49
+ FlightTracer can be used with either a list of aircraft IDs or a metadata URL that contains aircraft information. You can also configure AWS credentials if you wish to upload the output to S3. In addition, you can pass an AWS profile name if you have multiple sets of credentials in your environment.
50
+
51
+ ### Example
52
+
53
+ Below is an example of how to use FlightTracer:
54
+
55
+ ~~~python
56
+ #!/usr/bin/env python
57
+ """
58
+ fetch_example.py
59
+
60
+ This example fetches real flight trace data, processes it (computing the continuous ping_time
61
+ and inferring separate flight legs based on time gaps), saves the results locally
62
+ with a filename that includes the ICAO code(s) and today's date, and optionally
63
+ uploads the CSV and GeoJSON files to S3 using a specified AWS profile.
64
+ It also shows how to generate a plot of the flight activity and save the plot as a PNG.
65
+ """
66
+
67
+ import os
68
+ from datetime import date, datetime
69
+ from flight_tracer import FlightTracer
70
+ import geopandas as gpd
71
+
72
+ # Option 1: Use explicit AWS credentials (via environment variables or directly)
73
+ aws_credentials = {
74
+ "aws_access_key_id": os.getenv("MY_AWS_ACCESS_KEY_ID"),
75
+ "aws_secret_access_key": os.getenv("MY_AWS_SECRET_ACCESS_KEY")
76
+ }
77
+
78
+ # Option 2: Alternatively, use an AWS profile from your environment
79
+ aws_profile = os.getenv("MY_AWS_PROFILE") # e.g., "my_profile_name"
80
+
81
+ # Initialize flight_tracer with either explicit credentials or an AWS profile.
82
+ # For example, to use explicit credentials:
83
+ # tracer = flight_tracer(aircraft_ids=["a97753"], aws_creds=aws_credentials)
84
+ # Or, to use an AWS profile:
85
+ tracer = FlightTracer(aircraft_ids=["a97753"], aws_profile=aws_profile)
86
+
87
+ # Define the date range for trace data
88
+ start = date(2025, 1, 28)
89
+ end = date(2025, 2, 1)
90
+
91
+ # Fetch raw flight trace data from ADS-B Exchange
92
+ print("Fetching raw flight trace data...")
93
+ raw_df = tracer.get_traces(start, end)
94
+ if raw_df.empty:
95
+ print("No valid data to process.")
96
+ else:
97
+ print("Raw data sample:")
98
+ print(raw_df.head())
99
+
100
+ # Process trace data into a GeoDataFrame (computing continuous ping_time in UTC
101
+ # and detecting flight leg changes based on time gaps). A new 'flight_leg' column is
102
+ # created that combines call_sign, flight_date, and leg_id.
103
+ print("\nProcessing flight data into a GeoDataFrame...")
104
+ gdf = tracer.process_flight_data(raw_df)
105
+
106
+ # Optionally, retain ground points:
107
+ # gdf = tracer.process_flight_data(raw_df, filter_ground=False)
108
+
109
+ print("Processed GeoDataFrame sample:")
110
+ print(gdf.head())
111
+
112
+ # Optionally, inspect the unique flight legs detected in the data.
113
+ print("\nUnique flight legs detected:")
114
+ print(gdf["flight_leg"].unique())
115
+
116
+ # Build dynamic filenames that include the ICAO code(s) and today's date
117
+ icao_str = "_".join(tracer.aircraft_ids)
118
+ date_str = datetime.today().strftime("%Y%m%d")
119
+ csv_filename = f"data/flight_traces_{icao_str}_{date_str}.csv"
120
+ geojson_filename = f"data/flight_traces_{icao_str}_{date_str}.geojson"
121
+
122
+ # Save the processed data locally as CSV and GeoJSON
123
+ gdf.to_csv(csv_filename, index=False)
124
+ gdf.to_file(geojson_filename, driver="GeoJSON")
125
+ print(f"\nSaved processed data locally as '{csv_filename}' and '{geojson_filename}'.")
126
+
127
+ # Export linestring geometry for each flight leg
128
+ linestring_geojson_filename = f"data/flight_traces_lines_{icao_str}_{date_str}.geojson"
129
+ tracer.export_linestring_geojson(gdf, linestring_geojson_filename)
130
+
131
+ # Optionally upload the processed files to S3
132
+ bucket_name = "your-bucket-name" # replace with your bucket name
133
+ csv_object_name = f"flight_tracer/flight_traces_{icao_str}_{date_str}.csv"
134
+ geojson_object_name = f"flight_tracer/flight_traces_{icao_str}_{date_str}.geojson"
135
+ print("\nUploading files to S3 (if AWS credentials or profile are configured)...")
136
+ tracer.upload_to_s3(gdf, bucket_name, csv_object_name, geojson_object_name)
137
+ print("Upload process completed.")
138
+
139
+ # Plot the points with a basemap and optionally save the plot as a PNG
140
+ fig_filename = f"visuals/flight_map_{icao_str}_{date_str}.png"
141
+ tracer.plot_flights(gdf, geometry_type='points', figsize=(12,10), fig_filename=fig_filename)
142
+ ~~~
143
+
144
+ ## Configuration
145
+
146
+ The package supports the following configurations:
147
+
148
+ - **Aircraft IDs or metadata URL**: Provide either a list of ICAO codes or a metadata URL to extract aircraft information.
149
+ - **AWS credentials or profile**: Pass AWS credentials as a dictionary, specify an AWS profile, or set them as environment variables.
150
+ - **Date range**: Define the start and end dates to fetch trace data.
151
+
152
+ ## Notes
153
+
154
+ - Ensure that your AWS credentials or profile are configured correctly if you wish to use the S3 upload feature.
155
+ - The package fetches data from ADS-B Exchange so the availability of data depends on the public API.
156
+ - Flight leg detection is based on a configurable time gap threshold (default is 15 minutes). Adjust as needed for your data.
157
+ - Plotting functionality includes an option to expand the plotted extent (via `pad_factor`) for broader context and to save the plot as a PNG.
158
+
159
+ ## Roadmap for Enhancements
160
+
161
+ #### Enrich with external metadata:
162
+ - Build tools to “hydrate” your flight data with additional aircraft metadata from external sources such as FAA, ICAO, or other databases (e.g., aircraft type, operator, age, etc.).
163
+
164
+ #### Package distribution:
165
+ - Finalize the code structure (including tests, documentation, and a setup script) and publish the package to PyPI for installation via pip.
166
+
167
+ #### Enhanced documentation and examples:
168
+ - Update the README with comprehensive, end-to-end examples for various scenarios (e.g., pulling all flights for a month of a particular aircraft, processing a list of aircraft from a metadata URL, etc.), including usage examples for linestring export, leg splitting, and metadata enrichment.
169
+
170
+ #### Configurable thresholds and options:
171
+ - Allow users to adjust parameters like the time-gap threshold for determining new flight legs, output formats, and which metadata fields to hydrate.
172
+
173
+ #### Performance and error handling improvements:
174
+ - Add caching or parallel processing options to improve performance when fetching data over a long date range or for multiple aircraft.
175
+ - Improve error handling to gracefully skip problematic dates or flights while logging issues.
176
+
177
+ ## License
178
+
179
+ This project is licensed under the **Creative Commons CC0 1.0 Universal (CC0 1.0) Public Domain Dedication**.
180
+
181
+ This means that to the extent possible under law, the creator has waived all copyright and related or neighboring rights to this work.
182
+
183
+ You can copy, modify, distribute, and perform the work, even for commercial purposes, all without asking permission.
184
+
185
+ For more details, refer to the full [CC0 1.0 Universal License](https://creativecommons.org/publicdomain/zero/1.0/legalcode).
186
+
187
+ ![CC0 Badge](https://licensebuttons.net/p/zero/1.0/88x31.png)
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ setup.cfg
5
+ setup.py
6
+ flight_tracer/__init__.py
7
+ flight_tracer/core.py
8
+ flight_tracer.egg-info/PKG-INFO
9
+ flight_tracer.egg-info/SOURCES.txt
10
+ flight_tracer.egg-info/dependency_links.txt
11
+ flight_tracer.egg-info/requires.txt
12
+ flight_tracer.egg-info/top_level.txt
13
+ tests/__init__.py
14
+ tests/test_core.py
@@ -0,0 +1,7 @@
1
+ requests
2
+ pandas
3
+ geopandas
4
+ boto3
5
+ matplotlib
6
+ contextily
7
+ shapely
@@ -0,0 +1,2 @@
1
+ flight_tracer
2
+ tests
@@ -0,0 +1,32 @@
1
+ [metadata]
2
+ name = flight_tracer
3
+ version = 0.1.0
4
+ description = A package to fetch, process, store and plot aircraft trace data from ADS-B Exchange.
5
+ author = Matt Stiles
6
+ author_email = mattstiles@gmail.com
7
+ license = CC0-1.0
8
+ license_files = LICENSE # This is OK
9
+ long_description = file: README.md
10
+ long_description_content_type = text/markdown
11
+ url = https://github.com/stiles/flight_tracer
12
+ classifiers =
13
+ Programming Language :: Python :: 3
14
+ License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
15
+ Operating System :: OS Independent
16
+
17
+ [options]
18
+ packages = find:
19
+ install_requires =
20
+ requests
21
+ pandas
22
+ geopandas
23
+ boto3
24
+ matplotlib
25
+ contextily
26
+ shapely
27
+ python_requires = >=3.7
28
+
29
+ [egg_info]
30
+ tag_build =
31
+ tag_date = 0
32
+
@@ -0,0 +1,29 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="flight_tracer",
5
+ version="0.1.0",
6
+ author="Matt Stiles",
7
+ author_email="mattstiles@gmail.com",
8
+ description="A package to fetch, process, store and plot aircraft trace data from ADS-B Exchange.",
9
+ long_description=open("README.md", encoding="utf-8").read(),
10
+ long_description_content_type="text/markdown",
11
+ url="https://github.com/stiles/flight_tracer",
12
+ packages=find_packages(),
13
+ install_requires=[
14
+ "requests",
15
+ "pandas",
16
+ "geopandas",
17
+ "boto3",
18
+ "matplotlib",
19
+ "contextily",
20
+ "shapely"
21
+ ],
22
+ classifiers=[
23
+ "Programming Language :: Python :: 3",
24
+ "License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
25
+ "Operating System :: OS Independent",
26
+ ],
27
+ license="CC0-1.0",
28
+ python_requires='>=3.7',
29
+ )
File without changes
@@ -0,0 +1,25 @@
1
+ import unittest
2
+ from datetime import date
3
+ from flight_tracer import FlightTracer
4
+
5
+ class TestFlightTracer(unittest.TestCase):
6
+ def test_generate_urls_single_day(self):
7
+ # Create a FlightTracer instance with a dummy ICAO list
8
+ tracer = FlightTracer(aircraft_ids=["0d086e"])
9
+ # Using a single day: Jan 1, 2025 to Jan 1, 2025
10
+ urls = tracer.generate_urls(date(2025, 1, 1), date(2025, 1, 1))
11
+ # Expect 1 URL for a single day
12
+ self.assertEqual(len(urls), 1)
13
+ # Verify that the URL contains the correct date parts and ICAO code
14
+ expected_url = "https://globe.adsbexchange.com/globe_history/2025/01/01/traces/6e/trace_full_0d086e.json"
15
+ self.assertEqual(urls[0][0], expected_url)
16
+
17
+ def test_generate_urls_multiple_days(self):
18
+ tracer = FlightTracer(aircraft_ids=["0d086e"])
19
+ # Using two days: Jan 1, 2025 to Jan 2, 2025
20
+ urls = tracer.generate_urls(date(2025, 1, 1), date(2025, 1, 2))
21
+ # Expect 2 URLs (one for each day)
22
+ self.assertEqual(len(urls), 2)
23
+
24
+ if __name__ == '__main__':
25
+ unittest.main()