aissegments 0.2.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,41 @@
1
+ # Python build artefacts
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .eggs/
9
+ *.so
10
+ .pytest_cache/
11
+ .coverage
12
+ .coverage.*
13
+ htmlcov/
14
+ coverage.xml
15
+ .tox/
16
+ .nox/
17
+ .qgis-settings/
18
+
19
+ # Editors / IDEs
20
+ .vscode/
21
+ .idea/
22
+ *.swp
23
+ *.swo
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Virtualenvs
30
+ .venv/
31
+ venv/
32
+ env/
33
+
34
+ # Hatch
35
+ .hatch/
36
+
37
+ # Ruff cache
38
+ .ruff_cache/
39
+
40
+ # mypy
41
+ .mypy_cache/
@@ -0,0 +1,32 @@
1
+ cff-version: 1.2.0
2
+ title: "AISsegments: AIS trajectory segmentation and feature-preserving compression"
3
+ message: "If you use AISsegments in academic work, please cite both this software and the underlying TDKC algorithm paper."
4
+ type: software
5
+ authors:
6
+ - family-names: Hörteborn
7
+ given-names: Axel
8
+ email: axel.horteborn@ri.se
9
+ affiliation: "RISE Research Institutes of Sweden"
10
+ license: MIT
11
+ repository-code: "https://github.com/axelHorteborn/AISsegments"
12
+ keywords:
13
+ - AIS
14
+ - maritime
15
+ - trajectory
16
+ - compression
17
+ - TDKC
18
+
19
+ references:
20
+ - type: article
21
+ title: "An adaptive trajectory compression and feature preservation method for maritime traffic analysis"
22
+ authors:
23
+ - family-names: Guo
24
+ given-names: Shaoqing
25
+ - family-names: Bolbot
26
+ given-names: Victor
27
+ - family-names: Valdez Banda
28
+ given-names: Osiris
29
+ journal: "Ocean Engineering"
30
+ volume: 312
31
+ year: 2024
32
+ doi: "10.1016/j.oceaneng.2024.119189"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Axel Hörteborn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: aissegments
3
+ Version: 0.2.0
4
+ Summary: AIS trajectory segmentation and feature-preserving compression for maritime traffic analysis.
5
+ Project-URL: Homepage, https://github.com/axelHorteborn/AISsegments
6
+ Project-URL: Repository, https://github.com/axelHorteborn/AISsegments
7
+ Project-URL: Issues, https://github.com/axelHorteborn/AISsegments/issues
8
+ Author-email: Axel Hörteborn <axel.horteborn@ri.se>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: AIS,Douglas-Peucker,TDKC,compression,maritime,segmentation,trajectory
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Classifier: Topic :: Scientific/Engineering :: GIS
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: numpy>=1.24
24
+ Provides-Extra: aisdb
25
+ Requires-Dist: aisdb>=1.0; extra == 'aisdb'
26
+ Provides-Extra: dev
27
+ Requires-Dist: aisdb>=1.7; extra == 'dev'
28
+ Requires-Dist: matplotlib>=3.7; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=4; extra == 'dev'
30
+ Requires-Dist: pytest>=7; extra == 'dev'
31
+ Requires-Dist: ruff>=0.5; extra == 'dev'
32
+ Provides-Extra: viz
33
+ Requires-Dist: matplotlib>=3.7; extra == 'viz'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # AISsegments
37
+
38
+ **A Python toolkit for compressing AIS vessel trajectories into compact, query-friendly linestring segments — without losing the kinematic features that maritime safety and risk analysis depend on.**
39
+
40
+ ## What is this?
41
+
42
+ `aissegments` takes raw AIS position reports (millions of pings per day in a busy sea area) and outputs a small number of **constant-COG/SOG linestring segments per vessel**, one row per (vessel, time-window, course/speed). The output is shaped to drop straight into a PostGIS `LINESTRING` table.
43
+
44
+ It is the reference Python implementation of the **Top-Down Kinematic Compression (TDKC)** algorithm of Guo, Bolbot & Valdez Banda (Ocean Engineering 312 (2024), 119189), with the recursion-termination and adaptive-threshold fixes described in the paper.
45
+
46
+ ## Goals
47
+
48
+ 1. **Shrink AIS data without losing the maritime-relevant features.** A continent-scale AIS feed easily produces hundreds of millions of position reports per month. Most of those points are uninformative — a vessel cruising in a straight line at a steady speed needs only its endpoints. TDKC keeps the points where vessels actually do something interesting (turn, accelerate, stop, manoeuvre) and drops the rest.
49
+ 2. **Treat AIS as a sequence of *kinematic states*, not just positions.** The classical Douglas-Peucker simplification looks only at how far points stray from a straight line. That throws away course and speed changes that lie on a straight track, which are exactly the events maritime risk analysis cares about. TDKC uses both position (Synchronous Euclidean Distance) **and** velocity (Synchronous Velocity Difference) with adaptive per-track thresholds.
50
+ 3. **Produce database-ready output.** The output isn't a smaller list of points — it's a list of `Segment` records with start/end coordinates, mean COG/SOG, and the count of original observations spanned. Each segment is a 2-point `LINESTRING` ready for `ST_Intersects` and other PostGIS operations.
51
+ 4. **Stay framework-neutral.** The core depends only on NumPy. AISdb is an optional adapter (`pip install "aissegments[aisdb]"`); other input paths (CSV from Marine Cadastre / institutional exports / your own pipeline) work via [`read_csv_tracks`](src/aissegments/adapters.py) without any extra dependencies.
52
+
53
+ ## Who is this for?
54
+
55
+ - **Maritime risk analysts** who need to run collision/grounding/allision queries against millions of vessel positions per area-year and want the spatial+temporal index to fit in memory.
56
+ - **AIS data engineers** maintaining a Postgres/PostGIS warehouse of vessel tracks and looking for a principled way to densify ingestion without overwhelming storage.
57
+ - **Researchers** reproducing or extending Guo et al.'s adaptive trajectory compression work.
58
+
59
+ ## What it produces
60
+
61
+ - `tdkc(track)` — same `Track` interface but with only the *key points* retained (typically 1-5% of the input, depending on track shape and threshold tuning).
62
+ - `tdkc_segments(track)` — a list of `Segment` records, one per consecutive key-point pair, ready for direct insertion as PostGIS `LINESTRING(start_lon start_lat, end_lon end_lat)` geometries with `cog_mean`, `sog_mean`, and `n_points` (count of original AIS pings each segment represents).
63
+
64
+ ## Why not just use Douglas-Peucker?
65
+
66
+ DP and its variants throw away every point that lies on a straight line, regardless of whether the vessel's *behaviour* is changing. A vessel slowing from 15 to 5 knots while continuing to head east — DP keeps two points (start, end) and you lose the entire speed change. TDKC keeps the deceleration point because its **velocity vector** has shifted. See [`docs/algorithm.md`](docs/algorithm.md) for the precise math, and [`examples/output/03_min_svd_sweep.png`](examples/output/) for a visual side-by-side.
67
+
68
+ ## Companion package
69
+
70
+ [OMRAT](https://github.com/axelande/OMRAT) (Open Maritime Risk Analysis Tool) — a QGIS plugin for collision/grounding/allision risk modelling — uses AISsegments as its segment-ingestion backend. The OMRAT pipeline shows a complete end-to-end flow: NMEA / CSV → aisdb decode → TDKC compression → bulk-load into a year-partitioned PostGIS schema.
71
+
72
+ ## Install
73
+
74
+ ```bash
75
+ pip install aissegments
76
+
77
+ # with the optional AISdb adapter for ingestion from raw NMEA / CSV
78
+ pip install "aissegments[aisdb]"
79
+ ```
80
+
81
+ For development with full test + coverage tooling:
82
+
83
+ ```bash
84
+ git clone https://github.com/axelHorteborn/AISsegments
85
+ cd AISsegments
86
+ pip install -e ".[dev,aisdb]"
87
+ pytest --cov
88
+ ```
89
+
90
+ ## Quickstart
91
+
92
+ ```python
93
+ import numpy as np
94
+ from aissegments import Track, tdkc, tdkc_segments
95
+
96
+ # Build a Track from your own arrays (lat/lon in degrees, sog in knots, cog in degrees).
97
+ track = Track.from_arrays(
98
+ mmsi=219000123,
99
+ t=np.array([0, 60, 120, 180, 240], dtype=float), # unix seconds
100
+ lon=np.array([12.0, 12.001, 12.002, 12.003, 12.004]),
101
+ lat=np.array([55.0, 55.0, 55.0, 55.0, 55.0]),
102
+ sog=np.array([10.0, 10.0, 10.0, 10.0, 10.0]),
103
+ cog=np.array([90.0, 90.0, 90.0, 90.0, 90.0]),
104
+ )
105
+
106
+ # Compress: returns a Track containing only the key points.
107
+ compressed = tdkc(track)
108
+ print(len(compressed), "key points kept out of", len(track))
109
+
110
+ # Or go straight to segment records (one per consecutive key-point pair).
111
+ segments = tdkc_segments(track)
112
+ for s in segments:
113
+ print(s.t_start, s.t_end, s.cog_mean, s.sog_mean, s.n_points)
114
+ ```
115
+
116
+ ## Using AISdb as an input adapter
117
+
118
+ `aissegments` can consume the per-vessel track dicts produced by [AISdb](https://github.com/AISViz/AISdb)'s `TrackGen()`:
119
+
120
+ ```python
121
+ import aisdb
122
+ from aissegments.adapters import from_aisdb_track
123
+ from aissegments import tdkc_segments
124
+
125
+ with aisdb.SQLiteDBConn(dbpath="ais.db") as conn:
126
+ qry = aisdb.DBQuery(start=..., end=..., callback=aisdb.sql_query_strs.in_bbox_time)
127
+ tracks = aisdb.TrackGen(qry.gen_qry(), decimate=False)
128
+ for t_dict in tracks:
129
+ track = from_aisdb_track(t_dict)
130
+ for seg in tdkc_segments(track):
131
+ ... # write seg to your PostGIS table
132
+ ```
133
+
134
+ ## What's in the package
135
+
136
+ | Module | Purpose |
137
+ | --- | --- |
138
+ | [`aissegments.tdkc`](src/aissegments/tdkc.py) | TDKC algorithm: SED + SVD, Compression Binary Tree, adaptive thresholds, key-node identification |
139
+ | [`aissegments._types`](src/aissegments/_types.py) | `Track` and `Segment` dataclasses, `to_segments` helper |
140
+ | [`aissegments.adapters`](src/aissegments/adapters.py) | Input adapters: `from_aisdb_track`, `read_csv_tracks` (Marine Cadastre etc.), `read_csv_static_records` for vessel-info extraction |
141
+
142
+ ## Algorithm details
143
+
144
+ See [docs/algorithm.md](docs/algorithm.md) for the mathematical formulation, with equation references back to the source paper.
145
+
146
+ ## Citation
147
+
148
+ If you use this package in academic work, please cite both the software and the underlying paper:
149
+
150
+ > Guo, S., Bolbot, V., & Valdez Banda, O. (2024). An adaptive trajectory compression and feature preservation method for maritime traffic analysis. *Ocean Engineering*, 312, 119189. https://doi.org/10.1016/j.oceaneng.2024.119189
151
+
152
+ A `CITATION.cff` is included so GitHub renders a "Cite this repository" widget.
153
+
154
+ ## License
155
+
156
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,121 @@
1
+ # AISsegments
2
+
3
+ **A Python toolkit for compressing AIS vessel trajectories into compact, query-friendly linestring segments — without losing the kinematic features that maritime safety and risk analysis depend on.**
4
+
5
+ ## What is this?
6
+
7
+ `aissegments` takes raw AIS position reports (millions of pings per day in a busy sea area) and outputs a small number of **constant-COG/SOG linestring segments per vessel**, one row per (vessel, time-window, course/speed). The output is shaped to drop straight into a PostGIS `LINESTRING` table.
8
+
9
+ It is the reference Python implementation of the **Top-Down Kinematic Compression (TDKC)** algorithm of Guo, Bolbot & Valdez Banda (Ocean Engineering 312 (2024), 119189), with the recursion-termination and adaptive-threshold fixes described in the paper.
10
+
11
+ ## Goals
12
+
13
+ 1. **Shrink AIS data without losing the maritime-relevant features.** A continent-scale AIS feed easily produces hundreds of millions of position reports per month. Most of those points are uninformative — a vessel cruising in a straight line at a steady speed needs only its endpoints. TDKC keeps the points where vessels actually do something interesting (turn, accelerate, stop, manoeuvre) and drops the rest.
14
+ 2. **Treat AIS as a sequence of *kinematic states*, not just positions.** The classical Douglas-Peucker simplification looks only at how far points stray from a straight line. That throws away course and speed changes that lie on a straight track, which are exactly the events maritime risk analysis cares about. TDKC uses both position (Synchronous Euclidean Distance) **and** velocity (Synchronous Velocity Difference) with adaptive per-track thresholds.
15
+ 3. **Produce database-ready output.** The output isn't a smaller list of points — it's a list of `Segment` records with start/end coordinates, mean COG/SOG, and the count of original observations spanned. Each segment is a 2-point `LINESTRING` ready for `ST_Intersects` and other PostGIS operations.
16
+ 4. **Stay framework-neutral.** The core depends only on NumPy. AISdb is an optional adapter (`pip install "aissegments[aisdb]"`); other input paths (CSV from Marine Cadastre / institutional exports / your own pipeline) work via [`read_csv_tracks`](src/aissegments/adapters.py) without any extra dependencies.
17
+
18
+ ## Who is this for?
19
+
20
+ - **Maritime risk analysts** who need to run collision/grounding/allision queries against millions of vessel positions per area-year and want the spatial+temporal index to fit in memory.
21
+ - **AIS data engineers** maintaining a Postgres/PostGIS warehouse of vessel tracks and looking for a principled way to densify ingestion without overwhelming storage.
22
+ - **Researchers** reproducing or extending Guo et al.'s adaptive trajectory compression work.
23
+
24
+ ## What it produces
25
+
26
+ - `tdkc(track)` — same `Track` interface but with only the *key points* retained (typically 1-5% of the input, depending on track shape and threshold tuning).
27
+ - `tdkc_segments(track)` — a list of `Segment` records, one per consecutive key-point pair, ready for direct insertion as PostGIS `LINESTRING(start_lon start_lat, end_lon end_lat)` geometries with `cog_mean`, `sog_mean`, and `n_points` (count of original AIS pings each segment represents).
28
+
29
+ ## Why not just use Douglas-Peucker?
30
+
31
+ DP and its variants throw away every point that lies on a straight line, regardless of whether the vessel's *behaviour* is changing. A vessel slowing from 15 to 5 knots while continuing to head east — DP keeps two points (start, end) and you lose the entire speed change. TDKC keeps the deceleration point because its **velocity vector** has shifted. See [`docs/algorithm.md`](docs/algorithm.md) for the precise math, and [`examples/output/03_min_svd_sweep.png`](examples/output/) for a visual side-by-side.
32
+
33
+ ## Companion package
34
+
35
+ [OMRAT](https://github.com/axelande/OMRAT) (Open Maritime Risk Analysis Tool) — a QGIS plugin for collision/grounding/allision risk modelling — uses AISsegments as its segment-ingestion backend. The OMRAT pipeline shows a complete end-to-end flow: NMEA / CSV → aisdb decode → TDKC compression → bulk-load into a year-partitioned PostGIS schema.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install aissegments
41
+
42
+ # with the optional AISdb adapter for ingestion from raw NMEA / CSV
43
+ pip install "aissegments[aisdb]"
44
+ ```
45
+
46
+ For development with full test + coverage tooling:
47
+
48
+ ```bash
49
+ git clone https://github.com/axelHorteborn/AISsegments
50
+ cd AISsegments
51
+ pip install -e ".[dev,aisdb]"
52
+ pytest --cov
53
+ ```
54
+
55
+ ## Quickstart
56
+
57
+ ```python
58
+ import numpy as np
59
+ from aissegments import Track, tdkc, tdkc_segments
60
+
61
+ # Build a Track from your own arrays (lat/lon in degrees, sog in knots, cog in degrees).
62
+ track = Track.from_arrays(
63
+ mmsi=219000123,
64
+ t=np.array([0, 60, 120, 180, 240], dtype=float), # unix seconds
65
+ lon=np.array([12.0, 12.001, 12.002, 12.003, 12.004]),
66
+ lat=np.array([55.0, 55.0, 55.0, 55.0, 55.0]),
67
+ sog=np.array([10.0, 10.0, 10.0, 10.0, 10.0]),
68
+ cog=np.array([90.0, 90.0, 90.0, 90.0, 90.0]),
69
+ )
70
+
71
+ # Compress: returns a Track containing only the key points.
72
+ compressed = tdkc(track)
73
+ print(len(compressed), "key points kept out of", len(track))
74
+
75
+ # Or go straight to segment records (one per consecutive key-point pair).
76
+ segments = tdkc_segments(track)
77
+ for s in segments:
78
+ print(s.t_start, s.t_end, s.cog_mean, s.sog_mean, s.n_points)
79
+ ```
80
+
81
+ ## Using AISdb as an input adapter
82
+
83
+ `aissegments` can consume the per-vessel track dicts produced by [AISdb](https://github.com/AISViz/AISdb)'s `TrackGen()`:
84
+
85
+ ```python
86
+ import aisdb
87
+ from aissegments.adapters import from_aisdb_track
88
+ from aissegments import tdkc_segments
89
+
90
+ with aisdb.SQLiteDBConn(dbpath="ais.db") as conn:
91
+ qry = aisdb.DBQuery(start=..., end=..., callback=aisdb.sql_query_strs.in_bbox_time)
92
+ tracks = aisdb.TrackGen(qry.gen_qry(), decimate=False)
93
+ for t_dict in tracks:
94
+ track = from_aisdb_track(t_dict)
95
+ for seg in tdkc_segments(track):
96
+ ... # write seg to your PostGIS table
97
+ ```
98
+
99
+ ## What's in the package
100
+
101
+ | Module | Purpose |
102
+ | --- | --- |
103
+ | [`aissegments.tdkc`](src/aissegments/tdkc.py) | TDKC algorithm: SED + SVD, Compression Binary Tree, adaptive thresholds, key-node identification |
104
+ | [`aissegments._types`](src/aissegments/_types.py) | `Track` and `Segment` dataclasses, `to_segments` helper |
105
+ | [`aissegments.adapters`](src/aissegments/adapters.py) | Input adapters: `from_aisdb_track`, `read_csv_tracks` (Marine Cadastre etc.), `read_csv_static_records` for vessel-info extraction |
106
+
107
+ ## Algorithm details
108
+
109
+ See [docs/algorithm.md](docs/algorithm.md) for the mathematical formulation, with equation references back to the source paper.
110
+
111
+ ## Citation
112
+
113
+ If you use this package in academic work, please cite both the software and the underlying paper:
114
+
115
+ > Guo, S., Bolbot, V., & Valdez Banda, O. (2024). An adaptive trajectory compression and feature preservation method for maritime traffic analysis. *Ocean Engineering*, 312, 119189. https://doi.org/10.1016/j.oceaneng.2024.119189
116
+
117
+ A `CITATION.cff` is included so GitHub renders a "Cite this repository" widget.
118
+
119
+ ## License
120
+
121
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,102 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "aissegments"
7
+ version = "0.2.0"
8
+ description = "AIS trajectory segmentation and feature-preserving compression for maritime traffic analysis."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [
12
+ { name = "Axel Hörteborn", email = "axel.horteborn@ri.se" },
13
+ ]
14
+ requires-python = ">=3.10"
15
+ dependencies = [
16
+ "numpy>=1.24",
17
+ ]
18
+ keywords = ["AIS", "maritime", "trajectory", "compression", "TDKC", "Douglas-Peucker", "segmentation"]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Science/Research",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Scientific/Engineering",
29
+ "Topic :: Scientific/Engineering :: GIS",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ aisdb = ["aisdb>=1.0"]
34
+ viz = ["matplotlib>=3.7"]
35
+ dev = [
36
+ "pytest>=7",
37
+ "pytest-cov>=4",
38
+ "ruff>=0.5",
39
+ "matplotlib>=3.7",
40
+ "aisdb>=1.7",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/axelHorteborn/AISsegments"
45
+ Repository = "https://github.com/axelHorteborn/AISsegments"
46
+ Issues = "https://github.com/axelHorteborn/AISsegments/issues"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/aissegments"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = [
53
+ "src/aissegments",
54
+ "tests",
55
+ "README.md",
56
+ "LICENSE",
57
+ "CITATION.cff",
58
+ "pyproject.toml",
59
+ ]
60
+ # Local AIS data files dropped under tests/data/ are kept out of the
61
+ # distribution — they can be large (tens of MB) and may have third-party
62
+ # attribution requirements that are easier to manage out-of-band. The
63
+ # directory's README.md is small and documents the format, so it stays in.
64
+ exclude = [
65
+ "tests/data/*.csv",
66
+ "tests/data/*.csv.gz",
67
+ ]
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ addopts = [
72
+ "--strict-markers",
73
+ "--strict-config",
74
+ "-ra",
75
+ ]
76
+ markers = [
77
+ "aisdb: integration tests that require the optional aisdb package and its bundled test data",
78
+ "local_csv: integration tests that need a CSV file dropped under tests/data/",
79
+ ]
80
+
81
+ [tool.coverage.run]
82
+ source = ["aissegments"]
83
+ branch = true
84
+
85
+ [tool.coverage.report]
86
+ fail_under = 100
87
+ exclude_also = [
88
+ "if TYPE_CHECKING:",
89
+ "raise NotImplementedError",
90
+ "@overload",
91
+ "pragma: no cover",
92
+ ]
93
+ show_missing = true
94
+ skip_covered = false
95
+
96
+ [tool.ruff]
97
+ line-length = 100
98
+ target-version = "py310"
99
+
100
+ [tool.ruff.lint]
101
+ select = ["E", "F", "I", "N", "UP", "B", "SIM", "RUF"]
102
+ ignore = ["E501"] # line-length handled by formatter
@@ -0,0 +1,37 @@
1
+ """AIS trajectory segmentation and feature-preserving compression.
2
+
3
+ Public API:
4
+
5
+ - :class:`Track` / :class:`Segment` — typed input/output containers.
6
+ - :func:`to_segments` — pair consecutive points of a Track into Segment records.
7
+ - :func:`tdkc` — Top-Down Kinematic Compression; returns a compressed Track.
8
+ - :func:`tdkc_segments` — convenience wrapper: TDKC plus segment construction
9
+ with original-point-count enrichment.
10
+
11
+ Reference
12
+ ---------
13
+ Guo, S., Bolbot, V., & Valdez Banda, O. (2024). An adaptive trajectory
14
+ compression and feature preservation method for maritime traffic analysis.
15
+ *Ocean Engineering*, 312, 119189.
16
+ """
17
+ from aissegments._types import Segment, Track, to_segments
18
+ from aissegments.adapters import (
19
+ from_aisdb_track,
20
+ read_csv_static_records,
21
+ read_csv_tracks,
22
+ )
23
+ from aissegments.tdkc import tdkc, tdkc_segments
24
+
25
+ __version__ = "0.2.0"
26
+
27
+ __all__ = [
28
+ "Segment",
29
+ "Track",
30
+ "__version__",
31
+ "from_aisdb_track",
32
+ "read_csv_static_records",
33
+ "read_csv_tracks",
34
+ "tdkc",
35
+ "tdkc_segments",
36
+ "to_segments",
37
+ ]
@@ -0,0 +1,153 @@
1
+ """Typed containers for AIS tracks and the linestring-segment records they yield."""
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Iterable
5
+ from dataclasses import dataclass
6
+
7
+ import numpy as np
8
+
9
+ _FLOAT_FIELDS = ("t", "lon", "lat", "sog", "cog")
10
+
11
+
12
+ @dataclass
13
+ class Track:
14
+ """An ordered AIS track for a single vessel.
15
+
16
+ All array fields must be the same length and ``t`` must be
17
+ monotonically non-decreasing. Construct via :meth:`from_arrays` to get
18
+ consistent ``float64`` dtypes; the constructor itself only validates.
19
+
20
+ Attributes
21
+ ----------
22
+ mmsi : int
23
+ Maritime Mobile Service Identity for the vessel.
24
+ t : numpy.ndarray
25
+ Unix timestamps in seconds since epoch (UTC). Shape ``(N,)``.
26
+ lon, lat : numpy.ndarray
27
+ WGS84 longitude/latitude in degrees. Shape ``(N,)``.
28
+ sog : numpy.ndarray
29
+ Speed over ground in knots. Shape ``(N,)``.
30
+ cog : numpy.ndarray
31
+ Course over ground in degrees, ``[0, 360)``. Shape ``(N,)``.
32
+ """
33
+
34
+ mmsi: int
35
+ t: np.ndarray
36
+ lon: np.ndarray
37
+ lat: np.ndarray
38
+ sog: np.ndarray
39
+ cog: np.ndarray
40
+
41
+ def __post_init__(self) -> None:
42
+ n = len(self.t)
43
+ for name in _FLOAT_FIELDS[1:]:
44
+ arr = getattr(self, name)
45
+ if len(arr) != n:
46
+ raise ValueError(
47
+ f"Track field {name!r} has length {len(arr)}, expected {n}"
48
+ )
49
+ if n > 1:
50
+ diffs = np.diff(self.t)
51
+ if np.any(diffs < 0):
52
+ raise ValueError("Track.t must be monotonically non-decreasing")
53
+
54
+ def __len__(self) -> int:
55
+ return len(self.t)
56
+
57
+ @classmethod
58
+ def from_arrays(
59
+ cls,
60
+ mmsi: int,
61
+ t: Iterable[float],
62
+ lon: Iterable[float],
63
+ lat: Iterable[float],
64
+ sog: Iterable[float],
65
+ cog: Iterable[float],
66
+ ) -> Track:
67
+ """Coerce Python iterables / mixed dtypes into a validated ``Track``."""
68
+ return cls(
69
+ mmsi=int(mmsi),
70
+ t=np.asarray(list(t), dtype=np.float64),
71
+ lon=np.asarray(list(lon), dtype=np.float64),
72
+ lat=np.asarray(list(lat), dtype=np.float64),
73
+ sog=np.asarray(list(sog), dtype=np.float64),
74
+ cog=np.asarray(list(cog), dtype=np.float64),
75
+ )
76
+
77
+ def take(self, indices: Iterable[int]) -> Track:
78
+ """Return a new ``Track`` containing only the points at ``indices``."""
79
+ idx = np.asarray(list(indices), dtype=np.int64)
80
+ return Track(
81
+ mmsi=self.mmsi,
82
+ t=self.t[idx],
83
+ lon=self.lon[idx],
84
+ lat=self.lat[idx],
85
+ sog=self.sog[idx],
86
+ cog=self.cog[idx],
87
+ )
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class Segment:
92
+ """A constant-COG/SOG linestring between two key AIS points.
93
+
94
+ Attributes
95
+ ----------
96
+ mmsi : int
97
+ Vessel identifier.
98
+ t_start, t_end : float
99
+ Unix-second timestamps of the segment endpoints.
100
+ lon_start, lat_start, lon_end, lat_end : float
101
+ WGS84 coordinates (degrees) of the endpoints.
102
+ cog_mean, sog_mean : float
103
+ Mean course (degrees) and speed (knots) across the segment endpoints.
104
+ n_points : int
105
+ Number of *original* AIS points represented by the segment, including
106
+ both endpoints. ``2`` if the segment was built without a backing
107
+ original track; ``>=2`` after enrichment by :func:`tdkc_segments`.
108
+ """
109
+
110
+ mmsi: int
111
+ t_start: float
112
+ t_end: float
113
+ lon_start: float
114
+ lat_start: float
115
+ lon_end: float
116
+ lat_end: float
117
+ cog_mean: float
118
+ sog_mean: float
119
+ n_points: int = 2
120
+
121
+
122
+ def to_segments(track: Track) -> list[Segment]:
123
+ """Pair consecutive points of ``track`` into ``Segment`` records.
124
+
125
+ Each segment carries ``n_points = 2`` since the source track is treated as
126
+ already-compressed (or already-key-point-only). Use
127
+ :func:`aissegments.tdkc_segments` if you need the original-point-count
128
+ enrichment.
129
+ """
130
+ n = len(track)
131
+ if n < 2:
132
+ return []
133
+ segments: list[Segment] = []
134
+ for i in range(n - 1):
135
+ # Course wrap: take the shorter of the two arc directions when the
136
+ # endpoints straddle 0/360. Mean is then re-wrapped to [0, 360).
137
+ cog_diff = ((track.cog[i + 1] - track.cog[i] + 180.0) % 360.0) - 180.0
138
+ cog_mean = (track.cog[i] + cog_diff / 2.0) % 360.0
139
+ segments.append(
140
+ Segment(
141
+ mmsi=track.mmsi,
142
+ t_start=float(track.t[i]),
143
+ t_end=float(track.t[i + 1]),
144
+ lon_start=float(track.lon[i]),
145
+ lat_start=float(track.lat[i]),
146
+ lon_end=float(track.lon[i + 1]),
147
+ lat_end=float(track.lat[i + 1]),
148
+ cog_mean=float(cog_mean),
149
+ sog_mean=float((track.sog[i] + track.sog[i + 1]) / 2.0),
150
+ n_points=2,
151
+ )
152
+ )
153
+ return segments