strait-observatory 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.
- strait_observatory-0.1.0/PKG-INFO +181 -0
- strait_observatory-0.1.0/README.md +148 -0
- strait_observatory-0.1.0/pyproject.toml +42 -0
- strait_observatory-0.1.0/setup.cfg +4 -0
- strait_observatory-0.1.0/strait/__init__.py +35 -0
- strait_observatory-0.1.0/strait/aggregate.py +68 -0
- strait_observatory-0.1.0/strait/cutout.py +241 -0
- strait_observatory-0.1.0/strait/data/__init__.py +1 -0
- strait_observatory-0.1.0/strait/data/sentinel1.py +129 -0
- strait_observatory-0.1.0/strait/detect/__init__.py +203 -0
- strait_observatory-0.1.0/strait/validate.py +98 -0
- strait_observatory-0.1.0/strait/zones.py +38 -0
- strait_observatory-0.1.0/strait_observatory.egg-info/PKG-INFO +181 -0
- strait_observatory-0.1.0/strait_observatory.egg-info/SOURCES.txt +17 -0
- strait_observatory-0.1.0/strait_observatory.egg-info/dependency_links.txt +1 -0
- strait_observatory-0.1.0/strait_observatory.egg-info/requires.txt +17 -0
- strait_observatory-0.1.0/strait_observatory.egg-info/top_level.txt +1 -0
- strait_observatory-0.1.0/tests/test_integration.py +63 -0
- strait_observatory-0.1.0/tests/test_strait.py +234 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strait-observatory
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Satellite vessel detection & port activity monitoring from Sentinel-1 SAR
|
|
5
|
+
Author: Sivasubramanian S.
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/siva-sub/strait
|
|
8
|
+
Project-URL: Documentation, https://strait.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/siva-sub/strait
|
|
10
|
+
Keywords: sentinel-1,sar,vessel,detection,port,maritime,geospatial
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: numpy>=1.24
|
|
20
|
+
Requires-Dist: scipy>=1.10
|
|
21
|
+
Requires-Dist: pandas>=2.0
|
|
22
|
+
Requires-Dist: geopandas>=0.13
|
|
23
|
+
Requires-Dist: rasterio>=1.3
|
|
24
|
+
Requires-Dist: shapely>=2.0
|
|
25
|
+
Provides-Extra: ais
|
|
26
|
+
Requires-Dist: websockets>=11.0; extra == "ais"
|
|
27
|
+
Provides-Extra: viz
|
|
28
|
+
Requires-Dist: matplotlib>=3.7; extra == "viz"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# strait — satellite vessel detection & port activity monitoring
|
|
35
|
+
|
|
36
|
+
A Python package for detecting vessels from Sentinel-1 SAR imagery and
|
|
37
|
+
measuring port activity from satellite data — any port, any time.
|
|
38
|
+
|
|
39
|
+
## The one thing
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import strait
|
|
43
|
+
|
|
44
|
+
cutout = strait.Cutout(
|
|
45
|
+
module="sentinel1",
|
|
46
|
+
x=slice(103.4, 104.6),
|
|
47
|
+
y=slice(1.0, 1.6),
|
|
48
|
+
time=slice("2021-01", "2026-09"),
|
|
49
|
+
)
|
|
50
|
+
cutout.prepare() # download + process scenes
|
|
51
|
+
detections = cutout.detect() # CFAR vessel detection
|
|
52
|
+
monthly = cutout.aggregate(detections, zones={"eastern": (104.0, 1.24, 104.35, 1.40)})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That gives you monthly vessel counts per zone from free satellite radar.
|
|
56
|
+
|
|
57
|
+
## Why this exists
|
|
58
|
+
|
|
59
|
+
Ports publish trade statistics with a 2-4 week lag. Satellite radar
|
|
60
|
+
sees ships at anchor immediately, day or night, cloud or clear.
|
|
61
|
+
This package turns that satellite data into economic indicators.
|
|
62
|
+
|
|
63
|
+
It was built for the Singapore Strait Observatory project, where
|
|
64
|
+
radar-derived anchorage presence explains 48% of bunker sales variance
|
|
65
|
+
(R²=0.478, detrended, weather-robust, validated against AIS).
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install strait-observatory
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## What it does
|
|
74
|
+
|
|
75
|
+
| Layer | What | Output |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `Cutout` | Spatial/temporal subset + data source abstraction | xarray Dataset |
|
|
78
|
+
| `detect()` | Vessel detection (trimmed CFAR) | GeoDataFrame of detections |
|
|
79
|
+
| `aggregate()` | Zone × time aggregation | Monthly/weekly/daily counts |
|
|
80
|
+
| `AIS` | Validation against live/historical AIS | Precision/recall metrics |
|
|
81
|
+
| `Stats` | Join with official trade statistics | Correlation results |
|
|
82
|
+
|
|
83
|
+
## Data sources
|
|
84
|
+
|
|
85
|
+
| Source | What | Auth |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Copernicus Sentinel-1 | SAR radar imagery | Free CDSE account |
|
|
88
|
+
| AISStream.io | Live vessel AIS | Free API key |
|
|
89
|
+
| AISHub.net | Community AIS | Free membership |
|
|
90
|
+
| Mendeley (historical) | Port AIS datasets | Open download |
|
|
91
|
+
| S2Coast-2023 | 10m coastline (land mask) | Zenodo, open |
|
|
92
|
+
|
|
93
|
+
## Quick start
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install strait-observatory
|
|
97
|
+
export CDSE_USER=your@email
|
|
98
|
+
export CDSE_PASSWORD=your_password
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import strait
|
|
103
|
+
|
|
104
|
+
# 1. Define your area and time
|
|
105
|
+
cutout = strait.Cutout(
|
|
106
|
+
module="sentinel1",
|
|
107
|
+
x=slice(103.4, 104.6), # longitude
|
|
108
|
+
y=slice(1.0, 1.6), # latitude
|
|
109
|
+
time=slice("2021-01", "2026-09"),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# 2. Download and process (first time takes ~1h for 5 years)
|
|
113
|
+
cutout.prepare()
|
|
114
|
+
|
|
115
|
+
# 3. Detect vessels
|
|
116
|
+
detections = cutout.detect(method="trimmed_cfar")
|
|
117
|
+
|
|
118
|
+
# 4. Define anchorage zones (or use built-in Singapore zones)
|
|
119
|
+
zones = strait.Zones.singapore_strait()
|
|
120
|
+
monthly = cutout.aggregate(detections, zones, freq="MS")
|
|
121
|
+
|
|
122
|
+
# 5. Validate against AIS (optional)
|
|
123
|
+
ais = strait.AIS(source="aisstream", api_key="...")
|
|
124
|
+
match = ais.match(detections, threshold_m=500)
|
|
125
|
+
|
|
126
|
+
# 6. Correlate with official statistics (optional)
|
|
127
|
+
stats = strait.Stats.from_datagov_sg()
|
|
128
|
+
results = stats.correlate(monthly, target="bunker_sales")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Architecture (inspired by [atlite](https://github.com/PyPSA/atlite))
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
strait/
|
|
135
|
+
├── __init__.py # exports Cutout, detect, aggregate, AIS, Stats
|
|
136
|
+
├── cutout.py # Cutout class (spatial/temporal abstraction)
|
|
137
|
+
├── detect/
|
|
138
|
+
│ ├── __init__.py # detect() dispatcher
|
|
139
|
+
│ ├── cfar.py # classic CFAR (v3.1)
|
|
140
|
+
│ ├── trimmed_cfar.py # trimmed CFAR (v4, from SAR literature)
|
|
141
|
+
│ └── land_mask.py # coastline-based land mask
|
|
142
|
+
├── data/
|
|
143
|
+
│ ├── __init__.py # data source registry
|
|
144
|
+
│ ├── sentinel1.py # Sentinel-1 via CDSE (Sentinel Hub + OData)
|
|
145
|
+
│ ├── ais.py # AIS from multiple sources
|
|
146
|
+
│ └── official.py # Official statistics (data.gov.sg, etc.)
|
|
147
|
+
├── aggregate.py # zone × time aggregation
|
|
148
|
+
├── validate.py # SAR-AIS matching, precision/recall
|
|
149
|
+
├── stats.py # econometric correlation
|
|
150
|
+
└── zones.py # built-in zone definitions
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Built-in zones
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# Singapore Strait (from the observatory project)
|
|
157
|
+
zones = strait.Zones.singapore_strait()
|
|
158
|
+
|
|
159
|
+
# Define your own
|
|
160
|
+
zones = strait.Zones({
|
|
161
|
+
"my_anchorage": (104.0, 1.24, 104.35, 1.40), # lon_min, lat_min, lon_max, lat_max
|
|
162
|
+
"port_area": (103.68, 1.20, 104.02, 1.34),
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
169
|
+
|
|
170
|
+
## Citation
|
|
171
|
+
|
|
172
|
+
If you use this in research, cite the Singapore Strait Observatory:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
@software{strait_observatory_2026,
|
|
176
|
+
title = {strait: satellite vessel detection and port activity monitoring},
|
|
177
|
+
author = {Sivasubramanian, S.},
|
|
178
|
+
year = {2026},
|
|
179
|
+
url = {https://github.com/siva-sub/strait}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# strait — satellite vessel detection & port activity monitoring
|
|
2
|
+
|
|
3
|
+
A Python package for detecting vessels from Sentinel-1 SAR imagery and
|
|
4
|
+
measuring port activity from satellite data — any port, any time.
|
|
5
|
+
|
|
6
|
+
## The one thing
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import strait
|
|
10
|
+
|
|
11
|
+
cutout = strait.Cutout(
|
|
12
|
+
module="sentinel1",
|
|
13
|
+
x=slice(103.4, 104.6),
|
|
14
|
+
y=slice(1.0, 1.6),
|
|
15
|
+
time=slice("2021-01", "2026-09"),
|
|
16
|
+
)
|
|
17
|
+
cutout.prepare() # download + process scenes
|
|
18
|
+
detections = cutout.detect() # CFAR vessel detection
|
|
19
|
+
monthly = cutout.aggregate(detections, zones={"eastern": (104.0, 1.24, 104.35, 1.40)})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That gives you monthly vessel counts per zone from free satellite radar.
|
|
23
|
+
|
|
24
|
+
## Why this exists
|
|
25
|
+
|
|
26
|
+
Ports publish trade statistics with a 2-4 week lag. Satellite radar
|
|
27
|
+
sees ships at anchor immediately, day or night, cloud or clear.
|
|
28
|
+
This package turns that satellite data into economic indicators.
|
|
29
|
+
|
|
30
|
+
It was built for the Singapore Strait Observatory project, where
|
|
31
|
+
radar-derived anchorage presence explains 48% of bunker sales variance
|
|
32
|
+
(R²=0.478, detrended, weather-robust, validated against AIS).
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install strait-observatory
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## What it does
|
|
41
|
+
|
|
42
|
+
| Layer | What | Output |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `Cutout` | Spatial/temporal subset + data source abstraction | xarray Dataset |
|
|
45
|
+
| `detect()` | Vessel detection (trimmed CFAR) | GeoDataFrame of detections |
|
|
46
|
+
| `aggregate()` | Zone × time aggregation | Monthly/weekly/daily counts |
|
|
47
|
+
| `AIS` | Validation against live/historical AIS | Precision/recall metrics |
|
|
48
|
+
| `Stats` | Join with official trade statistics | Correlation results |
|
|
49
|
+
|
|
50
|
+
## Data sources
|
|
51
|
+
|
|
52
|
+
| Source | What | Auth |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Copernicus Sentinel-1 | SAR radar imagery | Free CDSE account |
|
|
55
|
+
| AISStream.io | Live vessel AIS | Free API key |
|
|
56
|
+
| AISHub.net | Community AIS | Free membership |
|
|
57
|
+
| Mendeley (historical) | Port AIS datasets | Open download |
|
|
58
|
+
| S2Coast-2023 | 10m coastline (land mask) | Zenodo, open |
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install strait-observatory
|
|
64
|
+
export CDSE_USER=your@email
|
|
65
|
+
export CDSE_PASSWORD=your_password
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import strait
|
|
70
|
+
|
|
71
|
+
# 1. Define your area and time
|
|
72
|
+
cutout = strait.Cutout(
|
|
73
|
+
module="sentinel1",
|
|
74
|
+
x=slice(103.4, 104.6), # longitude
|
|
75
|
+
y=slice(1.0, 1.6), # latitude
|
|
76
|
+
time=slice("2021-01", "2026-09"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# 2. Download and process (first time takes ~1h for 5 years)
|
|
80
|
+
cutout.prepare()
|
|
81
|
+
|
|
82
|
+
# 3. Detect vessels
|
|
83
|
+
detections = cutout.detect(method="trimmed_cfar")
|
|
84
|
+
|
|
85
|
+
# 4. Define anchorage zones (or use built-in Singapore zones)
|
|
86
|
+
zones = strait.Zones.singapore_strait()
|
|
87
|
+
monthly = cutout.aggregate(detections, zones, freq="MS")
|
|
88
|
+
|
|
89
|
+
# 5. Validate against AIS (optional)
|
|
90
|
+
ais = strait.AIS(source="aisstream", api_key="...")
|
|
91
|
+
match = ais.match(detections, threshold_m=500)
|
|
92
|
+
|
|
93
|
+
# 6. Correlate with official statistics (optional)
|
|
94
|
+
stats = strait.Stats.from_datagov_sg()
|
|
95
|
+
results = stats.correlate(monthly, target="bunker_sales")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Architecture (inspired by [atlite](https://github.com/PyPSA/atlite))
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
strait/
|
|
102
|
+
├── __init__.py # exports Cutout, detect, aggregate, AIS, Stats
|
|
103
|
+
├── cutout.py # Cutout class (spatial/temporal abstraction)
|
|
104
|
+
├── detect/
|
|
105
|
+
│ ├── __init__.py # detect() dispatcher
|
|
106
|
+
│ ├── cfar.py # classic CFAR (v3.1)
|
|
107
|
+
│ ├── trimmed_cfar.py # trimmed CFAR (v4, from SAR literature)
|
|
108
|
+
│ └── land_mask.py # coastline-based land mask
|
|
109
|
+
├── data/
|
|
110
|
+
│ ├── __init__.py # data source registry
|
|
111
|
+
│ ├── sentinel1.py # Sentinel-1 via CDSE (Sentinel Hub + OData)
|
|
112
|
+
│ ├── ais.py # AIS from multiple sources
|
|
113
|
+
│ └── official.py # Official statistics (data.gov.sg, etc.)
|
|
114
|
+
├── aggregate.py # zone × time aggregation
|
|
115
|
+
├── validate.py # SAR-AIS matching, precision/recall
|
|
116
|
+
├── stats.py # econometric correlation
|
|
117
|
+
└── zones.py # built-in zone definitions
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Built-in zones
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Singapore Strait (from the observatory project)
|
|
124
|
+
zones = strait.Zones.singapore_strait()
|
|
125
|
+
|
|
126
|
+
# Define your own
|
|
127
|
+
zones = strait.Zones({
|
|
128
|
+
"my_anchorage": (104.0, 1.24, 104.35, 1.40), # lon_min, lat_min, lon_max, lat_max
|
|
129
|
+
"port_area": (103.68, 1.20, 104.02, 1.34),
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
136
|
+
|
|
137
|
+
## Citation
|
|
138
|
+
|
|
139
|
+
If you use this in research, cite the Singapore Strait Observatory:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
@software{strait_observatory_2026,
|
|
143
|
+
title = {strait: satellite vessel detection and port activity monitoring},
|
|
144
|
+
author = {Sivasubramanian, S.},
|
|
145
|
+
year = {2026},
|
|
146
|
+
url = {https://github.com/siva-sub/strait}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "strait-observatory"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Satellite vessel detection & port activity monitoring from Sentinel-1 SAR"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Sivasubramanian S."},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["sentinel-1", "sar", "vessel", "detection", "port", "maritime", "geospatial"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
dependencies = [
|
|
26
|
+
"numpy>=1.24",
|
|
27
|
+
"scipy>=1.10",
|
|
28
|
+
"pandas>=2.0",
|
|
29
|
+
"geopandas>=0.13",
|
|
30
|
+
"rasterio>=1.3",
|
|
31
|
+
"shapely>=2.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
ais = ["websockets>=11.0"]
|
|
36
|
+
viz = ["matplotlib>=3.7"]
|
|
37
|
+
dev = ["pytest>=7.0", "pytest-cov", "ruff"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/siva-sub/strait"
|
|
41
|
+
Documentation = "https://strait.readthedocs.io"
|
|
42
|
+
Repository = "https://github.com/siva-sub/strait"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""strait — satellite vessel detection & port activity monitoring.
|
|
2
|
+
|
|
3
|
+
Detect vessels from Sentinel-1 SAR imagery. Measure port activity
|
|
4
|
+
from space. Any port, any time, no ground infrastructure needed.
|
|
5
|
+
|
|
6
|
+
Inspired by atlite (PyPSA): the Cutout abstraction makes a complex
|
|
7
|
+
satellite-data pipeline usable in 5 lines of code.
|
|
8
|
+
|
|
9
|
+
Use cases:
|
|
10
|
+
- Port activity nowcasting (satellite → economic indicator)
|
|
11
|
+
- Anchorage congestion monitoring (are anchorages full?)
|
|
12
|
+
- Dark vessel detection (SAR detections without AIS match)
|
|
13
|
+
- Bunkering activity estimation (anchored tanker counts)
|
|
14
|
+
- Research: vessel presence time series for econometrics
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
__author__ = "Sivasubramanian S."
|
|
19
|
+
|
|
20
|
+
from .cutout import Cutout
|
|
21
|
+
from .zones import Zones
|
|
22
|
+
from .detect import detect_vessels, TRIMMED_CFAR, CLASSIC_CFAR
|
|
23
|
+
from .aggregate import aggregate
|
|
24
|
+
from .validate import AISMatch
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Cutout",
|
|
28
|
+
"Zones",
|
|
29
|
+
"detect_vessels",
|
|
30
|
+
"aggregate",
|
|
31
|
+
"AISMatch",
|
|
32
|
+
"TRIMMED_CFAR",
|
|
33
|
+
"CLASSIC_CFAR",
|
|
34
|
+
"__version__",
|
|
35
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Aggregate vessel detections by zone and time period."""
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def aggregate(
|
|
8
|
+
detections,
|
|
9
|
+
zones: Optional[Dict[str, Tuple[float, float, float, float]]] = None,
|
|
10
|
+
freq: str = "MS",
|
|
11
|
+
) -> pd.DataFrame:
|
|
12
|
+
"""Aggregate detections by zone and time period.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
detections : geopandas.GeoDataFrame
|
|
17
|
+
Output from detect_vessels() — needs geometry + date columns
|
|
18
|
+
zones : dict
|
|
19
|
+
{"zone_name": (lon_min, lat_min, lon_max, lat_max)}
|
|
20
|
+
freq : str
|
|
21
|
+
Pandas frequency: "MS" (monthly), "W" (weekly), "D" (daily)
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
pd.DataFrame
|
|
26
|
+
Indexed by period, one column per zone (+ "total")
|
|
27
|
+
"""
|
|
28
|
+
if detections is None or len(detections) == 0:
|
|
29
|
+
return pd.DataFrame()
|
|
30
|
+
|
|
31
|
+
df = detections.copy()
|
|
32
|
+
|
|
33
|
+
# Assign zones
|
|
34
|
+
if zones:
|
|
35
|
+
df["zone"] = _assign_zones(df, zones)
|
|
36
|
+
else:
|
|
37
|
+
df["zone"] = "all"
|
|
38
|
+
|
|
39
|
+
# Parse dates if needed
|
|
40
|
+
if "date" in df.columns:
|
|
41
|
+
df["period"] = pd.to_datetime(df["date"], format="%Y%m", errors="coerce")
|
|
42
|
+
elif "scene_index" in df.columns:
|
|
43
|
+
df["period"] = df["scene_index"] # fallback to scene index
|
|
44
|
+
else:
|
|
45
|
+
df["period"] = pd.Timestamp.now()
|
|
46
|
+
|
|
47
|
+
# Group by period × zone
|
|
48
|
+
result = df.groupby([pd.Grouper(key="period", freq=freq), "zone"]).size()
|
|
49
|
+
result = result.unstack(fill_value=0)
|
|
50
|
+
|
|
51
|
+
# Add total
|
|
52
|
+
result["total"] = result.sum(axis=1)
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _assign_zones(df, zones):
|
|
58
|
+
"""Assign zone name to each detection based on lat/lon."""
|
|
59
|
+
zone_names = []
|
|
60
|
+
for _, row in df.iterrows():
|
|
61
|
+
lon, lat = row.geometry.x, row.geometry.y
|
|
62
|
+
assigned = "other"
|
|
63
|
+
for name, (lon_min, lat_min, lon_max, lat_max) in zones.items():
|
|
64
|
+
if lon_min <= lon <= lon_max and lat_min <= lat <= lat_max:
|
|
65
|
+
assigned = name
|
|
66
|
+
break
|
|
67
|
+
zone_names.append(assigned)
|
|
68
|
+
return zone_names
|