|
25 | 25 | import geopandas as gpd |
26 | 26 | import pandas as pd |
27 | 27 | from shapely.geometry import box |
| 28 | +import matplotlib.pyplot as plt |
| 29 | +import contextily as cx |
28 | 30 |
|
29 | 31 | import nomad.map_utils as nm |
30 | 32 | from nomad.city_gen import RasterCity |
31 | 33 | from nomad.traj_gen import Population |
| 34 | +from nomad.io.base import from_file |
32 | 35 | from tqdm import tqdm |
33 | 36 |
|
34 | 37 | # %% [markdown] |
35 | 38 | # ## Configuration |
36 | 39 |
|
37 | 40 | # %% |
38 | 41 | LARGE_BOX = box(-75.1905, 39.9235, -75.1425, 39.9535) |
| 42 | +MEDIUM_BOX = box(-75.1665, 39.9385, -75.1425, 39.9535) |
39 | 43 |
|
40 | 44 | USE_FULL_CITY = False |
41 | 45 | OUTPUT_DIR = Path("sandbox") |
|
45 | 49 | BOX_NAME = "full" |
46 | 50 | POLY = "Philadelphia, Pennsylvania, USA" |
47 | 51 | else: |
48 | | - BOX_NAME = "large" |
49 | | - POLY = LARGE_BOX |
| 52 | + BOX_NAME = "medium" |
| 53 | + POLY = MEDIUM_BOX |
50 | 54 |
|
51 | 55 | SANDBOX_GPKG = OUTPUT_DIR / f"sandbox_data_{BOX_NAME}.gpkg" |
52 | 56 | REGENERATE_DATA = False |
53 | 57 |
|
54 | 58 | config = { |
55 | 59 | "box_name": BOX_NAME, |
56 | | - "block_side_length": 15.0, |
57 | | - "hub_size": 600, |
58 | | - "N": 100, |
| 60 | + "block_side_length": 10.0, |
| 61 | + "hub_size": 100, |
| 62 | + "N": 10, |
59 | 63 | "name_seed": 42, |
60 | 64 | "name_count": 2, |
61 | 65 | "epr_params": { |
62 | 66 | "datetime": "2024-01-01 00:00-05:00", |
63 | | - "end_time": "2024-04-01 00:00-05:00", |
| 67 | + "end_time": "2024-01-08 00:00-05:00", # 7 days |
64 | 68 | "epr_time_res": 15, |
65 | 69 | "rho": 0.4, |
66 | 70 | "gamma": 0.3, |
67 | 71 | "seed_base": 100 |
| 72 | + }, |
| 73 | + "traj_params": { |
| 74 | + "dt": 0.5, |
| 75 | + "seed_base": 200 |
| 76 | + }, |
| 77 | + "sampling_params": { |
| 78 | + "beta_ping": 5, |
| 79 | + "beta_durations": None, |
| 80 | + "beta_start": None, |
| 81 | + "ha": 11.5/15, |
| 82 | + "seed_base": 300 |
68 | 83 | } |
69 | 84 | } |
70 | 85 |
|
|
179 | 194 | grav_time = time.time() - t3 |
180 | 195 | print(f"Gravity computation: {grav_time:>6.2f}s") |
181 | 196 |
|
182 | | -raster_time = gen_time + graph_time + hub_time + grav_time |
| 197 | +t4 = time.time() |
| 198 | +city.compute_shortest_paths(callable_only=True) |
| 199 | +paths_time = time.time() - t4 |
| 200 | +print(f"Shortest paths: {paths_time:>6.2f}s") |
| 201 | + |
| 202 | +raster_time = gen_time + graph_time + hub_time + grav_time + paths_time |
183 | 203 | print("-"*50) |
184 | 204 | print(f"Rasterization: {raster_time:>6.2f}s") |
185 | 205 | print("="*50) |
@@ -283,15 +303,128 @@ def get_size_mb(obj): |
283 | 303 | print(f"\nConfig saved to {config_path}") |
284 | 304 | print(f"Destination diaries saved to {dest_diaries_path}") |
285 | 305 |
|
| 306 | +# %% [markdown] |
| 307 | +# ## Generate Full Trajectories from Destination Diaries |
| 308 | + |
286 | 309 | # %% |
287 | | -import geopandas as gpd |
288 | | -import contextily as cx |
289 | | -import matplotlib.pyplot as plt |
| 310 | +print("\n" + "="*50) |
| 311 | +print("TRAJECTORY GENERATION") |
| 312 | +print("="*50) |
| 313 | + |
| 314 | +t1 = time.time() |
| 315 | +failed_agents = [] |
| 316 | +for i, agent in tqdm(enumerate(population.roster.values()), total=config["N"], desc="Generating trajectories"): |
| 317 | + try: |
| 318 | + agent.generate_trajectory( |
| 319 | + dt=config["traj_params"]["dt"], |
| 320 | + seed=config["traj_params"]["seed_base"] + i |
| 321 | + ) |
| 322 | + except ValueError as e: |
| 323 | + failed_agents.append((agent.identifier, str(e))) |
| 324 | + continue |
| 325 | + |
| 326 | +traj_gen_time = time.time() - t1 |
| 327 | +print(f"Trajectory generation: {traj_gen_time:>6.2f}s") |
| 328 | +if failed_agents: |
| 329 | + print(f"Warning: {len(failed_agents)} agents failed trajectory generation") |
| 330 | + |
| 331 | +total_points = sum(len(agent.trajectory) for agent in population.roster.values() if agent.trajectory is not None) |
| 332 | +print(f"Total trajectory points: {total_points:,}") |
| 333 | +print(f"Points per second: {total_points/traj_gen_time:.1f}") |
| 334 | +print("-"*50) |
| 335 | +print(f"Total trajectory: {traj_gen_time:>6.2f}s") |
| 336 | +print("="*50) |
| 337 | + |
| 338 | +# %% [markdown] |
| 339 | +# ## Sample Sparse Trajectories |
| 340 | + |
| 341 | +# %% |
| 342 | +print("\n" + "="*50) |
| 343 | +print("SPARSE TRAJECTORY SAMPLING") |
| 344 | +print("="*50) |
| 345 | + |
| 346 | +t1 = time.time() |
| 347 | +for i, agent in tqdm(enumerate(population.roster.values()), total=config["N"], desc="Sampling trajectories"): |
| 348 | + if agent.trajectory is None: |
| 349 | + continue |
| 350 | + agent.sample_trajectory( |
| 351 | + beta_ping=config["sampling_params"]["beta_ping"], |
| 352 | + beta_durations=config["sampling_params"]["beta_durations"], |
| 353 | + beta_start=config["sampling_params"]["beta_start"], |
| 354 | + ha=config["sampling_params"]["ha"], |
| 355 | + seed=config["sampling_params"]["seed_base"] + i, |
| 356 | + replace_sparse_traj=True |
| 357 | + ) |
| 358 | + |
| 359 | +sampling_time = time.time() - t1 |
| 360 | +print(f"Sparse sampling: {sampling_time:>6.2f}s") |
| 361 | + |
| 362 | +total_sparse_points = sum(len(agent.sparse_traj) for agent in population.roster.values() if agent.sparse_traj is not None) |
| 363 | +print(f"Total sparse points: {total_sparse_points:,}") |
| 364 | +print(f"Sparsity ratio: {total_sparse_points/total_points:.2%}") |
| 365 | +print("-"*50) |
| 366 | +print(f"Total sampling: {sampling_time:>6.2f}s") |
| 367 | +print("="*50) |
| 368 | + |
| 369 | +# %% [markdown] |
| 370 | +# ## Reproject to Mercator and Persist |
| 371 | + |
| 372 | +# %% |
| 373 | +print("\n" + "="*50) |
| 374 | +print("REPROJECTION AND PERSISTENCE") |
| 375 | +print("="*50) |
| 376 | + |
| 377 | +# Build POI data for diary reprojection |
| 378 | +cent = city.buildings_gdf['door_point'] if 'door_point' in city.buildings_gdf.columns else city.buildings_gdf.geometry.centroid |
| 379 | +poi_data = pd.DataFrame({ |
| 380 | + 'building_id': city.buildings_gdf['id'].values, |
| 381 | + 'x': (city.buildings_gdf['door_cell_x'].astype(float) + 0.5).values if 'door_cell_x' in city.buildings_gdf.columns else cent.x.values, |
| 382 | + 'y': (city.buildings_gdf['door_cell_y'].astype(float) + 0.5).values if 'door_cell_y' in city.buildings_gdf.columns else cent.y.values |
| 383 | +}) |
| 384 | + |
| 385 | +print("Reprojecting sparse trajectories to Web Mercator...") |
| 386 | +population.reproject_to_mercator(sparse_traj=True, full_traj=False, diaries=True, poi_data=poi_data) |
| 387 | + |
| 388 | +print("Saving sparse trajectories and diaries...") |
| 389 | +population.save_pop( |
| 390 | + sparse_path=OUTPUT_DIR / f"sparse_traj_{BOX_NAME}", |
| 391 | + diaries_path=OUTPUT_DIR / f"diaries_{BOX_NAME}", |
| 392 | + partition_cols=["date"], |
| 393 | + fmt='parquet' |
| 394 | +) |
| 395 | +print("-"*50) |
| 396 | +print("="*50) |
| 397 | + |
| 398 | +# %% [markdown] |
| 399 | +# ## Visualize Sparse Trajectories |
| 400 | + |
| 401 | +# %% |
| 402 | +print("\n" + "="*50) |
| 403 | +print("VISUALIZATION") |
| 404 | +print("="*50) |
| 405 | + |
| 406 | +# Read sparse trajectories |
| 407 | +sparse_traj_df = from_file(OUTPUT_DIR / f"sparse_traj_{BOX_NAME}", format="parquet") |
| 408 | +print(f"Loaded {len(sparse_traj_df):,} sparse trajectory points for {config['N']} agents") |
| 409 | + |
| 410 | +# Plot with contextily basemap |
| 411 | +fig, ax = plt.subplots(figsize=(12, 10)) |
| 412 | + |
| 413 | +# Plot each agent with different color |
| 414 | +for agent_id in sparse_traj_df['user_id'].unique(): |
| 415 | + agent_traj = sparse_traj_df[sparse_traj_df['user_id'] == agent_id] |
| 416 | + ax.scatter(agent_traj['x'], agent_traj['y'], s=1, alpha=0.5, label=agent_id) |
| 417 | + |
| 418 | +# Add basemap |
| 419 | +cx.add_basemap(ax, crs="EPSG:3857", source=cx.providers.CartoDB.Positron) |
290 | 420 |
|
291 | | -box = gpd.GeoSeries(city.boundary_polygon) |
292 | | -fig, ax = plt.subplots() |
293 | | -box.plot(ax=ax, facecolor="None") |
294 | | -cx.add_basemap(ax, crs=box.crs) |
| 421 | +ax.set_xlabel('X (Web Mercator)') |
| 422 | +ax.set_ylabel('Y (Web Mercator)') |
| 423 | +ax.set_title(f'Sparse Trajectories - {config["N"]} Agents, 7 Days') |
| 424 | +ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', markerscale=10) |
| 425 | +plt.tight_layout() |
| 426 | +plt.savefig(OUTPUT_DIR / f"sparse_trajectories_{BOX_NAME}.png", dpi=150, bbox_inches='tight') |
| 427 | +print(f"Saved plot to {OUTPUT_DIR / f'sparse_trajectories_{BOX_NAME}.png'}") |
295 | 428 | plt.show() |
296 | 429 |
|
297 | 430 | # %% |
0 commit comments