blob: 4d7d8aba1629e21eebfd274d99956efd65a965ef [file]
#!/usr/bin/env python3
"""Prepare TDrive dataset for TsFile vs Parquet benchmark.
TDrive taxi trajectories:
- ~10,000 taxis in Beijing over 1 week
- Schema: TAG(taxi_id), FIELD(longitude, latitude DOUBLE)
- 2 series per device (17778 series / 8889 devices in paper)
Download: https://www.microsoft.com/en-us/research/publication/
t-drive-driving-directions-based-on-taxi-trajectories/
Extract to --raw-dir.
Usage:
python3 prepare_tdrive.py --raw-dir ./raw/tdrive --out-dir ./prepared/tdrive
"""
import argparse
import csv
import json
import sys
from datetime import datetime
from pathlib import Path
def parse_taxi_file(filepath, taxi_id):
"""Parse a TDrive taxi trajectory file.
Format: taxi_id, datetime_string, longitude, latitude
"""
rows = []
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(",")
if len(parts) < 4:
continue
try:
dt_str = parts[1].strip()
lon = float(parts[2].strip())
lat = float(parts[3].strip())
# Skip invalid coordinates
if lon < 1 or lat < 1:
continue
dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
ts = int(dt.timestamp())
rows.append((ts, taxi_id, lon, lat))
except (ValueError, IndexError):
continue
return rows
def main():
parser = argparse.ArgumentParser(description="Prepare TDrive dataset")
parser.add_argument("--raw-dir", required=True,
help="Path to extracted TDrive taxi_log data")
parser.add_argument("--out-dir", required=True,
help="Output directory for prepared CSV")
args = parser.parse_args()
raw_dir = Path(args.raw_dir)
out_dir = Path(args.out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
# Find taxi files: {id}.txt (may be in a subdirectory like taxi_log_2008_by_id/)
taxi_files = sorted(raw_dir.glob("*.txt"),
key=lambda p: int(p.stem) if p.stem.isdigit() else 0)
if not taxi_files:
# Try subdirectories
taxi_files = sorted(raw_dir.rglob("*.txt"),
key=lambda p: int(p.stem) if p.stem.isdigit() else 0)
# Filter to only numeric-named files (skip Thumbs.db etc)
taxi_files = [f for f in taxi_files if f.stem.isdigit()]
if not taxi_files:
print(f"Error: no taxi .txt files found in {raw_dir}", file=sys.stderr)
sys.exit(1)
print(f"Found {len(taxi_files)} taxi files")
all_rows = []
devices = set()
for tf in taxi_files:
taxi_id = f"taxi_{tf.stem}"
rows = parse_taxi_file(tf, taxi_id)
if rows:
devices.add(taxi_id)
all_rows.extend(rows)
if len(devices) % 1000 == 0 and len(devices) > 0:
print(f" processed {len(devices)} taxis, "
f"{len(all_rows)} points so far...")
# Sort by taxi_id, then timestamp
all_rows.sort(key=lambda r: (r[1], r[0]))
# Deduplicate: TsFile requires unique timestamps per device.
before = len(all_rows)
deduped = []
for i, row in enumerate(all_rows):
if i + 1 < len(all_rows) and row[0] == all_rows[i + 1][0] \
and row[1] == all_rows[i + 1][1]:
continue
deduped.append(row)
all_rows = deduped
print(f"Deduplicated: {before} -> {len(all_rows)} "
f"(removed {before - len(all_rows)} duplicate timestamps)")
# Write CSV
csv_path = out_dir / "data_sorted.csv"
print(f"\nWriting {len(all_rows)} rows to {csv_path}...")
with open(csv_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["timestamp", "taxi_id", "longitude", "latitude"])
writer.writerows(all_rows)
# Write metadata
meta = {
"dataset": "tdrive",
"table_name": "tdrive",
"total_points": len(all_rows),
"num_devices": len(devices),
"num_series": len(devices) * 2, # lon, lat per device
"tags": [
{"name": "taxi_id", "type": "STRING"},
],
"fields": [
{"name": "longitude", "type": "DOUBLE"},
{"name": "latitude", "type": "DOUBLE"},
],
}
meta_path = out_dir / "meta.json"
with open(meta_path, "w") as f:
json.dump(meta, f, indent=2)
print(f"Done: {meta['total_points']} points, "
f"{meta['num_devices']} devices, "
f"{meta['num_series']} series")
if __name__ == "__main__":
main()