Skip to content

About

A Python tool for collecting AIS vessel data from MarineTraffic map endpoints, with geographic tile discovery, concurrent downloads, vessel deduplication, and JSON/CSV export.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

marine-traffic-data

A Python tool for collecting AIS vessel data from MarineTraffic map endpoints, with geographic tile discovery, concurrent downloads, vessel deduplication, and JSON/CSV export.

The script sweeps a rectangular geographic bounding box, converts it into the Web Mercator (XYZ) tile grid that the MarineTraffic map uses, requests each tile's vessel payload in parallel, merges the results into a single de-duplicated set of vessels, and writes them out as both JSON and CSV.

The bounding box shipped in the repository covers the seas around Iran (Persian Gulf, Gulf of Oman, Strait of Hormuz, and the southern Caspian Sea), but it is a four-line change to point it anywhere in the world.

How It Works

  1. Bounding box → tile range. latlon_to_tile() converts the corners of the configured NORTH / SOUTH / WEST / EAST box into XYZ tile coordinates using the standard Web Mercator projection. get_tile_range() normalises the two corners into an inclusive min_x..max_x, min_y..max_y rectangle (Y grows downward in Web Mercator, so the corners are sorted rather than assumed).

  2. Tile enumeration. Every (x, y) pair in that rectangle becomes one tile request. A box spanning 16 × 16 tiles produces 256 requests.

  3. Concurrent fetching. A ThreadPoolExecutor (4 workers by default) issues the requests against:

    https://www.marinetraffic.com/getData/get_data_json_4/z:{z}/X:{x}/Y:{y}/station:{station}
    

    Each worker uses its own requests.Session with a browser-like User-Agent and Referer, a 30 second timeout, and up to 3 attempts per tile with a 1 second pause between retries. Failures are captured per tile and never abort the whole run.

  4. Deduplication. Vessels near a tile border are returned by several neighbouring tiles. Results are collected into a dictionary keyed on SHIP_ID, so each vessel is stored exactly once (last write wins). Rows without a SHIP_ID are skipped.

  5. Export. The merged vessel list is written to iran_ships.json (UTF-8, indented, non-ASCII preserved) and iran_ships.csv. The CSV header is the sorted union of every key seen across all vessels, so tiles that return extra fields do not break the file.

Zoom levels

MarineTraffic's z parameter is offset by one from the standard XYZ zoom used to compute the tile numbers:

MARINE_Z = 9        # sent in the URL as z:9
XYZ_ZOOM = MARINE_Z - 1   # 8, used for the lat/lon -> tile math

Keep that relationship intact if you change the zoom — computing tiles at the wrong zoom yields tile numbers the endpoint will reject or that cover the wrong part of the planet.

Higher zoom means more, smaller tiles: each +1 on MARINE_Z roughly quadruples the request count while reducing the chance of hitting a per-tile vessel cap. Lower zoom means fewer requests but a higher chance that dense areas are truncated.

Requirements

Everything else (math, json, csv, time, concurrent.futures) is in the standard library.

Installation

git clone https://github.com/BaseMax/marine-traffic-data.git
cd marine-traffic-data

# optional but recommended
python -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate

pip install requests

Usage

Run the script from the directory where you want the output files to land:

python update.py

There are no command-line arguments — the run is configured by editing the constants at the top of update.py. Progress is printed to stdout as tiles complete, and a summary plus a preview of the first ten vessels is printed at the end.

A 256-tile sweep with 4 workers typically takes a few minutes, depending on how many tiles time out or need retries.

Configuration

All configuration lives in the constants near the top of update.py:

Constant Default Meaning
BASE_URL https://www.marinetraffic.com Host the tile endpoint is requested from.
NORTH 40.5 Northern edge of the bounding box, in degrees latitude.
SOUTH 23.0 Southern edge of the bounding box, in degrees latitude.
WEST 43.0 Western edge of the bounding box, in degrees longitude.
EAST 64.5 Eastern edge of the bounding box, in degrees longitude.
MARINE_Z 9 Zoom value sent to MarineTraffic as z:.
XYZ_ZOOM MARINE_Z - 1 Zoom used for the lat/lon → tile conversion. Keep it one below MARINE_Z.
STATION 0 station: path segment; 0 means "no specific AIS receiving station".

Two more values are set inside main() and fetch_tile():

Value Location Default Meaning
workers main() 4 Concurrent tile requests. Raise carefully — see Notes and Limitations.
retry count / timeout fetch_tile() 3 attempts, 30 s Per-tile resilience settings.

Changing the area

Replace the four bounds with the box you care about. A few examples:

# Persian Gulf only
NORTH, SOUTH, WEST, EAST = 30.5, 23.5, 47.5, 57.0

# Mediterranean Sea
NORTH, SOUTH, WEST, EAST = 46.0, 30.0, -6.0, 36.5

# English Channel and North Sea
NORTH, SOUTH, WEST, EAST = 58.0, 49.0, -5.0, 9.0

Remember that the request count grows with the area, not the width — doubling both the latitude and longitude span roughly quadruples the number of tiles. Latitudes are clamped in practice by Web Mercator, which is undefined beyond roughly ±85.05°.

Changing the output filenames

The output paths are the literals "iran_ships.json" and "iran_ships.csv" inside main(). Rename them there if you retarget the bounding box — nothing else depends on the names.

Output

Two files are written into the current working directory:

iran_ships.json

A JSON array of vessel objects, exactly as returned by the endpoint, deduplicated by SHIP_ID:

[
  {
    "SHIP_ID": "721954",
    "SHIPNAME": "KPS ORHAN ALI KHAN",
    "LAT": "30.20693",
    "LON": "47.875206",
    "SHIPTYPE": "3",
    "FLAG": "LR",
    "SPEED": "0",
    "COURSE": "285",
    "HEADING": "287"
  }
]

Written with ensure_ascii=False, so vessel names containing non-ASCII characters are preserved as-is rather than escaped.

iran_ships.csv

The same records in flat form. The header is the sorted union of every key present across all vessels, and extrasaction="ignore" means an unexpected key on a single record cannot crash the export. The file is written with the utf-8-sig encoding (UTF-8 with BOM) so Excel opens it with the correct encoding on Windows.

The CSV is only written when at least one vessel was collected; an empty run leaves the previous CSV untouched but still writes an empty JSON array.

Vessel Fields

The exact set of fields depends on what MarineTraffic returns for a given tile, but the commonly present ones are:

Field Description
SHIP_ID MarineTraffic's internal vessel identifier. Used as the deduplication key.
SHIPNAME Vessel name. May be blank, or an MMSI-looking number for vessels broadcasting no name.
MMSI Maritime Mobile Service Identity.
IMO IMO number, when known.
LAT / LON Last reported position in decimal degrees.
SPEED Speed over ground, in tenths of a knot.
COURSE Course over ground, in degrees.
HEADING Heading, in degrees; 511 conventionally means "not available".
SHIPTYPE Numeric vessel category (e.g. 2 fishing, 3 tug/special craft, 4 high-speed craft, 6 passenger, 7 cargo, 8 tanker).
FLAG ISO 3166-1 alpha-2 code of the flag state.
LENGTH / WIDTH Dimensions in metres, when reported.
DESTINATION Reported destination string.
ELAPSED Minutes since the last position report.

Values arrive as strings; cast them yourself if you need numeric types downstream.

Example Run

$ python update.py

MarineTraffic zoom: 9
XYZ zoom: 8

Tile range:
X: 158 -> 173
Y: 96 -> 111

Total tiles: 256

HTTP 400: 9/158/99
[1/256] 9/158/96 -> 0 ships
[2/256] 9/158/98 -> 0 ships
[3/256] 9/158/97 -> 0 ships
...
[72/256] 9/162/105 -> 160 ships
[75/256] 9/162/106 -> 598 ships
[76/256] 9/162/107 -> 242 ships
...
[175/256] 9/168/109 -> 626 ships
[254/256] 9/173/109 -> 7 ships
[255/256] 9/173/111 -> 8 ships
[256/256] 9/173/110 -> 6 ships

============================================================
DONE
============================================================
Tiles: 256
Unique ships: 4993

Saved:
  iran_ships.json
  iran_ships.csv

APANGJEONGCHO L B | 37.556862 47.053768 | TYPE: 1 | FLAG: KR | ID: 10656547
KPS ORHAN ALI KHAN | 30.20693 47.875206 | TYPE: 3 | FLAG: LR | ID: 721954
NAVIG8 MONTIEL | 30.199566 47.880501 | TYPE: 8 | FLAG: SG | ID: 758562
APNOIA | 30.185143 47.892658 | TYPE: 8 | FLAG: MT | ID: 754469
ABATHAR | 30.498837 47.861938 | TYPE: 3 | FLAG: IQ | ID: 659544
WASIT | 30.194233 47.884884 | TYPE: 3 | FLAG: IQ | ID: 7835870
MUKAFIH | 30.56214 47.790878 | TYPE: 3 | FLAG: IQ | ID: 659543
447804000 | 29.464212 47.818172 | TYPE: 2 | FLAG: KW | ID: 8913026
447011457 120 90 | 29.295443 47.818386 | TYPE: 0 | FLAG: KW | ID: 9728046
447008446 | 29.39065 47.829147 | TYPE: 0 | FLAG: KW | ID: 9588940

Note that the total across the per-tile counts is larger than Unique ships — that difference is the deduplication doing its job on vessels reported by more than one tile.

Notes and Limitations

  • HTTP 400 responses are normal. Tiles that fall entirely on land, or that the endpoint declines to serve, return 400. The script retries them three times and then records zero vessels for that tile. A run with many 400 lines is not a broken run.
  • Snapshot, not a stream. Each run captures vessel positions at one moment. To build a track history, run the script periodically and store the results with a timestamp.
  • Per-tile caps. Very dense tiles may be truncated by the endpoint. If a tile reports a suspiciously round large number, increase MARINE_Z so the same water is split across more, smaller tiles.
  • Be gentle with concurrency. The default of 4 workers is deliberately conservative. Pushing it high enough to matter is what gets an IP rate-limited or blocked, and the retry loop will not help you once that happens.
  • Unofficial endpoint. This is the internal endpoint the public map uses, not a documented API. Its URL shape, parameters, and response schema can change without warning, and code that depends on specific field names may need updating.
  • No pagination or incremental state. Every run refetches the whole box and overwrites the output files. Copy or rename the outputs between runs if you want to keep them.

Troubleshooting

Symptom Likely cause and fix
ModuleNotFoundError: No module named 'requests' Install the dependency: pip install requests.
Every tile returns 0 ships The bounding box may cover only land, or the endpoint changed. Verify the printed tile range corresponds to the water you expect.
Lots of HTTP 429 / connection errors You are being rate-limited. Lower workers, wait, and try again.
Unique ships: 0, no CSV written The CSV is only written when the vessel list is non-empty. Check the per-tile output above the summary for the underlying failure.
Garbled vessel names in Excel The CSV is already utf-8-sig; make sure you are opening the file directly rather than importing it with a mismatched encoding.
Tile range looks wrong Confirm XYZ_ZOOM is still MARINE_Z - 1, and that NORTH > SOUTH and EAST > WEST.

Project Structure

marine-traffic-data/
├── update.py     # the entire tool: tile math, fetching, dedup, export
├── LICENSE       # GNU GPL v3
└── README.md

Legal

This tool reads a publicly reachable endpoint that backs the MarineTraffic web map. It is provided for research and educational purposes. You are responsible for complying with MarineTraffic's terms of service and any laws that apply to you, and for keeping your request volume reasonable. For commercial or high-volume use, obtain data through MarineTraffic's official API instead.

This project is not affiliated with, endorsed by, or connected to MarineTraffic.

License

GNU General Public License v3.0 - see LICENSE.

Copyright 2026 © Seyyed Ali Mohammadiyeh (Max Base)

About

A Python tool for collecting AIS vessel data from MarineTraffic map endpoints, with geographic tile discovery, concurrent downloads, vessel deduplication, and JSON/CSV export.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages