diff --git a/geoplot.py b/geoplot.py index 6f264d1..a049a74 100644 --- a/geoplot.py +++ b/geoplot.py @@ -31,194 +31,228 @@ ``` """ -import re -import json +# Standard library imports for regex parsing and JSON serialization +import re # regular expressions for splitting variable paths +import json # encode Python objects as JSON -import pandas as pd -import numpy as np +# Third-party libraries for data manipulation +import pandas as pd # timestamp generation and time series handling +import numpy as np # numeric arrays and conversions -from string import Template +# Utilities for templating and nested state access +from string import Template # substitute placeholders in HTML template +# access nested state via a path from agent_torch.core.helpers import get_by_path +# HTML template for Cesium-based visualization with placeholders: +# $accessToken, $data, $startTime, $stopTime, $visualType geoplot_template = """ - - - - Cesium Time-Series Heatmap Visualization - - - - - -
- - + + + + Cesium Time-Series Heatmap Visualization + + + + + +
+ + """ def read_var(state, var): + """ + Retrieve a nested variable from a state dict given a slash-separated path. + """ return get_by_path(state, re.split("/", var)) class GeoPlot: + """Convert simulation state trajectories into Cesium visualizations.""" + def __init__(self, config, options): + # Store simulation config and visualization settings self.config = config ( self.cesium_token, @@ -227,27 +261,37 @@ def __init__(self, config, options): self.entity_property, self.visualization_type, ) = ( - options["cesium_token"], - options["step_time"], - options["coordinates"], - options["feature"], - options["visualization_type"], + options["cesium_token"], # Ion access token + options["step_time"], # seconds between steps + options["coordinates"], # path to position in state dict + options["feature"], # path to property values in state dict + options["visualization_type"], # 'color' or 'size' ) def render(self, state_trajectory): + """Generate GeoJSON and HTML files from a list of state dicts.""" coords, values = [], [] - name = self.config["simulation_metadata"]["name"] + name = self.config["simulation_metadata"]["name"] # base filename geodata_path, geoplot_path = f"{name}.geojson", f"{name}.html" + # Extract coordinates and feature values at each step for i in range(0, len(state_trajectory) - 1): final_state = state_trajectory[i][-1] - coords = np.array(read_var(final_state, self.entity_position)).tolist() + # Read the nested coordinate list and flatten to Python list + coords = np.array( + read_var( + final_state, + self.entity_position)).tolist() + # Read feature array, flatten, and store values.append( - np.array(read_var(final_state, self.entity_property)).flatten().tolist() + np.array(read_var(final_state, self.entity_property)) + .flatten() + .tolist() ) - start_time = pd.Timestamp.utcnow() + # Generate timestamps for each step in the simulation + start_time = pd.Timestamp.utcnow() # use UTC now as timeline start timestamps = [ start_time + pd.Timedelta(seconds=i * self.step_time) for i in range( @@ -256,15 +300,18 @@ def render(self, state_trajectory): ) ] - geojsons = [] + geojsons = [] # will hold FeatureCollections for each entity + # Build GeoJSON features per coordinate index for i, coord in enumerate(coords): features = [] for time, value_list in zip(timestamps, values): + # Create a GeoJSON Feature with geometry and time/value props features.append( { "type": "Feature", "geometry": { "type": "Point", + # Cesium expects [lon, lat] "coordinates": [coord[1], coord[0]], }, "properties": { @@ -273,11 +320,15 @@ def render(self, state_trajectory): }, } ) - geojsons.append({"type": "FeatureCollection", "features": features}) + # Wrap features into a FeatureCollection + geojsons.append( + {"type": "FeatureCollection", "features": features}) + # Write GeoJSON data to file for later loading in HTML with open(geodata_path, "w", encoding="utf-8") as f: json.dump(geojsons, f, ensure_ascii=False, indent=2) + # Fill HTML template placeholders and write out the final viewer page tmpl = Template(geoplot_template) with open(geoplot_path, "w", encoding="utf-8") as f: f.write(