Ocean Intelligence is a full-stack vessel research application built with ASP.NET Core, React, and TypeScript. It searches Global Fishing Watch by vessel identity or by historical presence within an area and date range, then provides a deeper identity and registry view for a selected vessel.
The application works with historical AIS observations. It does not provide live vessel positions, continuous tracking, or proof that a vessel remained inside an area for an entire reported period.
- Search a geographic bounding box and date range for observed vessel traffic.
- Find a vessel directly by name, MMSI, IMO, or callsign without generating an area report.
- Retain inputs, filters, and completed results in each search mode during the page session.
- Review identity matches, matching evidence, and observation dates; use
Load morefor additional provider pages. - Search dates default to a dynamic seven-day historical window ending five days ago.
- Filter results by vessel name, MMSI, IMO, or callsign, plus flag and vessel type.
- Sort results by sampled AIS hours or vessel name, with a deterministic tie-breaker.
- Results reveal progressively: 50 matching vessels first, with a
Show 50 moreaction. - A replacement search keeps previous results visible until the new report succeeds.
- View vessel identity, classification, flag, and sampled AIS presence hours.
- Select a result to load additional AIS identity and public registry records.
- Switch the detail panel between UTC and local time for every timestamp.
- Review vessel and gear classifications with their source and effective years.
- Preserve useful area-search values when detailed records contain empty fields.
- Cache vessel details in browser state to avoid repeated requests during a session.
- Display Global Fishing Watch attribution and relevant maritime-data caveats.
- Optional
Use my locationbutton that populates an approximate 25 nautical mile rectangular search area from browser geolocation. - Return consistent API errors using ASP.NET Core Problem Details.
- .NET 10 and ASP.NET Core controller-based Web API
- React 19, TypeScript, and Vite
- Native
fetchand local React state - xUnit
- Global Fishing Watch API v3
The project intentionally does not yet include authentication, a database, routing, Redux, Axios, a component library, or a map library.
ocean-intelligence/
|-- backend/
| `-- OceanIntelligence.Api/
| |-- Controllers/ HTTP endpoints
| |-- ErrorHandling/ External-service error mapping
| |-- Models/ Public API contracts
| |-- Protection/ Rate-limit policies and protection options
| `-- Services/GlobalFishingWatch/ GFW client and upstream models
|-- frontend/
| |-- src/
| | |-- api/ Typed browser API clients
| | |-- components/ Search, results, and detail UI
| | |-- hooks/ Vessel-search request and result state
| | `-- types/ Frontend API contracts
| `-- tests/ Nearby-search and vessel-search checks
|-- tests/
| `-- OceanIntelligence.Api.Tests/ Controller, client, mapping, and error tests
`-- OceanIntelligence.slnx
- .NET 10 SDK
- Node.js
22.22.2+(22.x),24.15+(24.x), or26+for the frontend and its Vitest/jsdom checks - npm
- A Global Fishing Watch account and API access token
- Git
Verify the required runtimes:
dotnet --version
node --version
npm --versionClone the repository:
git clone https://github.com/Brazenbillygoat/ocean-intelligence.git
cd ocean-intelligenceRestore backend and frontend dependencies:
dotnet restore OceanIntelligence.slnx
npm install --prefix frontendStore the GFW access token with .NET user secrets. Do not place the token in appsettings.json, an environment file, source code, or a Git commit.
$gfwToken = (Get-Clipboard).Trim()
dotnet user-secrets set "GlobalFishingWatch:AccessToken" $gfwToken --project backend/OceanIntelligence.Api
Remove-Variable gfwTokenThe non-sensitive GFW base URL is configured in backend/OceanIntelligence.Api/appsettings.json.
Start the API from the repository root:
dotnet run --project backend/OceanIntelligence.Api --launch-profile httpThe API listens at http://localhost:5131 by default. Its development launch profile does not open a browser.
In a second terminal, start the frontend:
cd frontend
npm run devThe Vite development server proxies relative /api requests to the local ASP.NET Core API.
The search form includes an optional Use my location button. Activating it requests browser geolocation and populates the four coordinate fields with an approximate 25 nautical mile rectangular search area centered on the reported position.
The button does not submit the form or start a search. After location succeeds, review the populated bounds and date range, then use Search vessels as usual.
The populated region is an approximate rectangle, not a true circle. It uses a fixed 25 nautical mile radius and the existing bounding-box area-search API. The application does not provide live vessel location, and the populated coordinates describe a historical AIS search area, not a current position.
If browser location is unavailable, denied, times out, or returns coordinates too close to a pole or the international date line, the coordinate fields are left unchanged and manual bounds remain available. No location data is stored in browser storage, cookies, or logs.
GET /api/vessel-traffic| Parameter | Meaning |
|---|---|
west |
Western longitude from -180 to 180 |
south |
Southern latitude from -90 to 90 |
east |
Eastern longitude from -180 to 180 |
north |
Northern latitude from -90 to 90 |
startDate |
Start date in YYYY-MM-DD format |
endDate |
End date in YYYY-MM-DD format |
The bounding box must have west less than east and south less than north. A report cannot span more than 366 days.
Example request from PowerShell:
$uri = "http://localhost:5131/api/vessel-traffic?west=-71.20&south=42.20&east=-70.70&north=42.60&startDate=2026-06-01&endDate=2026-06-08"
$vesselTraffic = Invoke-RestMethod -Uri $uri
$vesselTraffic.vessels | Select-Object -First 10The response contains the accepted query, result count, and vessels with identity, classification, observation boundaries, and sampled AIS presence hours.
The frontend fetches one complete report and then filters, sorts, and progressively reveals it locally. It shows 50 matching vessels first and adds 50 per Show 50 more action. No filtering, sorting, or Show more action makes an API request.
GET /api/vessels/search?query=MISS%20FREYAquery accepts 3-100 characters after trimming. The optional cursor is an opaque continuation value: send the previous response's nextCursor unchanged with the same query. A null nextCursor means there are no more pages.
The response contains the accepted query, normalized matches, nextCursor, dataset, attribution, and caveats. Each match has a stable matchKey, a nullable vesselId, names/identifiers/flag, record observation dates, and source-labeled matching evidence. Select a usable vesselId to request details; records without one remain visibly unavailable.
Lookup follows the GFW vessel-search contract using the public vessel-identity dataset, MATCH_CRITERIA, 30 provider entries per request, and the endpoint's since continuation token. A provider entry can contain multiple AIS identities. These remain separate in provider order even when they share an MMSI or name; duplicate identity keys are suppressed across loaded pages. Loaded identity counts are not verified counts of physical vessels. Matching evidence is labeled as result-group evidence because it can refer to another identity or registry record.
Successful lookup pages cache on the server for 30 minutes, keyed separately by query, dataset, and cursor. Lookup and details share the existing 60 requests/minute/client-IP budget. Failures and canceled lookups are not cached. Area protections remain independent.
The frontend submits only through Search and Load more. Replacements retain old results until success; errors identify the failed query, and pagination can be retried without losing matches. Mode changes cancel outstanding API requests and close details; returning makes no automatic request. Direct dossiers explicitly omit area-presence hours, entry/exit observations, and geographic/date report context. Identity/registry sections, research placeholders, attribution, caveats, and the UTC/local-time toggle remain available.
Search state stays in page memory, without persistent search history. Lookup responses use Cache-Control: no-store for clients. Default upstream HTTP URI logging is disabled to avoid retaining search queries; the GFW token stays server-only.
GET /api/vessels/{vesselId}This endpoint performs a lighter Global Fishing Watch identity lookup for one selected vessel. The response includes:
- AIS identity records and observation periods
- Public registry records and vessel specifications
- Combined vessel and gear classifications
- Dataset and provider information
- Attribution and data caveats
The detail panel shows timestamps in UTC by default, with a Show local time toggle that switches every presence, identity, and registry timestamp to the browser's local time zone.
The application deliberately does not request detailed tracks for every search result.
Global Fishing Watch data and AIS transmissions require careful interpretation:
- Area-search results describe historical observed presence, not live location.
- Presence hours come from sampled AIS activity and do not prove continuous transmission.
- Entry and exit observations are not exact geographic border-crossing times.
- AIS identity values are self reported and may be incomplete, outdated, or incorrect.
- Multiple identities may describe the same physical vessel.
- Registry records can conflict or change over time.
- Identity and registry observation dates are not vessel positions.
- Vessel and gear classifications can change as source data and models are revised.
Known Global Fishing Watch failures are translated into consistent API responses:
| Condition | API status |
|---|---|
| GFW rate limit | 503 Service Unavailable |
| GFW timeout | 504 Gateway Timeout |
| Other GFW failure | 502 Bad Gateway |
Errors use the standard ASP.NET Core ProblemDetails JSON shape.
Run frontend checks:
cd frontend
npm run test:nearby
npm run test:vessel-search
npm run lint
npm run buildRun backend tests and formatting verification from the repository root:
dotnet test OceanIntelligence.slnx
dotnet format OceanIntelligence.slnx --verify-no-changes --no-restoreBackend tests use in-memory HTTP handlers and do not call the live Global Fishing Watch API. The vessel-search suite uses Vitest, jsdom, and React Testing Library with mocked fetch responses, including requests that settle after cancellation. Application startup, live data, and browser/visual acceptance remain owner checks.
The Global Fishing Watch access token is required only by the backend and should be stored with .NET user secrets during local development. If a token is ever committed, revoke it immediately and remove it from Git history before publishing the repository.
Vessel data is provided by Global Fishing Watch. API use is subject to the Global Fishing Watch API terms, licensing requirements, rate limits, and data caveats.