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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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