blob: 5e75a0c03941f82ab1a07e3c87edc1316b716719 [file]
#!/usr/bin/env python3
"""Prepare GeoLife dataset for TsFile vs Parquet benchmark.
GeoLife GPS Trajectories:
- 182 users, GPS tracks of walking/driving/cycling
- Schema: TAG(user_id), FIELD(latitude, longitude, altitude DOUBLE)
- 3 series per device (543 series / 181 devices in paper)
Download: https://download.microsoft.com/download/F/4/8/
F4894AA5-FDBC-481E-9285-D5F8C4C4F039/
Geolife%20Trajectories%201.3.zip
Extract to --raw-dir (should contain Data/ subdirectory).
Usage:
python3 prepare_geolife.py --raw-dir ./raw/geolife --out-dir ./prepared/geolife
"""
import argparse
import csv
import json
import os
import sys
from datetime import datetime
from pathlib import Path
def parse_plt_file(filepath):
"""Parse a GeoLife .plt trajectory file.
First 6 lines are header. Data lines:
latitude, longitude, 0, altitude, days_since_1899, date, time
"""
rows = []
with open(filepath, "r") as f:
# Skip 6 header lines
for _ in range(6):
f.readline()
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(",")
if len(parts) < 7:
continue
try:
lat = float(parts[0])
lon = float(parts[1])
alt = float(parts[3])
# Parse date + time -> epoch seconds
dt_str = parts[5] + " " + parts[6]
dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
ts = int(dt.timestamp())
rows.append((ts, lat, lon, alt))
except (ValueError, IndexError):
continue
return rows
def main():
parser = argparse.ArgumentParser(description="Prepare GeoLife dataset")
parser.add_argument("--raw-dir", required=True,
help="Path to extracted GeoLife data (contains 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 Data directory
data_dir = raw_dir / "Data"
if not data_dir.exists():
data_dir = raw_dir # Maybe extracted directly
user_dirs = sorted([d for d in data_dir.iterdir()
if d.is_dir() and d.name.isdigit()])
if not user_dirs:
print(f"Error: no user directories found in {data_dir}", file=sys.stderr)
sys.exit(1)
print(f"Found {len(user_dirs)} users")
all_rows = []
devices = set()
for user_dir in user_dirs:
user_id = user_dir.name # e.g., "000", "001"
traj_dir = user_dir / "Trajectory"
if not traj_dir.exists():
continue
devices.add(user_id)
plt_files = sorted(traj_dir.glob("*.plt"))
user_points = 0
for plt_file in plt_files:
rows = parse_plt_file(plt_file)
for ts, lat, lon, alt in rows:
all_rows.append((ts, user_id, lat, lon, alt))
user_points += len(rows)
print(f" user {user_id}: {user_points} points, "
f"{len(plt_files)} trajectories")
# Sort by user_id, then timestamp
all_rows.sort(key=lambda r: (r[1], r[0]))
# Deduplicate: TsFile requires unique timestamps per device.
# For duplicate timestamps within the same device, keep last value.
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 # skip duplicate, keep later one
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", "user_id", "latitude", "longitude",
"altitude"])
writer.writerows(all_rows)
# Write metadata
meta = {
"dataset": "geolife",
"table_name": "geolife",
"total_points": len(all_rows),
"num_devices": len(devices),
"num_series": len(devices) * 3, # lat, lon, alt per device
"tags": [
{"name": "user_id", "type": "STRING"},
],
"fields": [
{"name": "latitude", "type": "DOUBLE"},
{"name": "longitude", "type": "DOUBLE"},
{"name": "altitude", "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()