Fires

This application demonstrates a simulation of a schedule of fires given geospatial locations and specified datetimes (at one minute resolution)

The application contains a single Environment class which listens to the time status published by the manager application and publishes fire information at the specified ignition datetime. The application also contains callback messages that updates datetime in the fires DataFrame for each of ignition (including latitude-longitude GeographicPosition), detection, and reporting.


Class

class Environment(app, fires)

Bases: Observer

The Environment object class inherits properties from the Observer object class in the NOS-T tools library

app

An application containing a test-run namespace, a name and description for the app, client credentials, and simulation timing instructions

Type:

ManagedApplication

fires

Dataframe of scenario scheduled fires including fireId (int), fire ignition (datetime), and fire latitude-longitude location (GeographicPosition)

Type:

DataFrame

Environment.on_change(source, property_name, old_value, new_value)

Standard on_change callback function format inherited from Observer object class

In this instance, the callback function checks the simulation datetime against each scheduled fire ignition datetime for the scenario. If past the scheduled start of a fire, a FireStarted message is sent to PREFIX.fire.location:

        if property_name == "time":
            new_fires = self.fires[
                (self.fires.start <= new_value) & (self.fires.start > old_value)
            ]
            for index, fire in new_fires.iterrows():
                self.app.send_message(
                    self.app.app_name,
                    "location",
                    FireStarted(
                        fireId=fire.fireId,
                        start=fire.start,
                        latitude=fire.latitude,
                        longitude=fire.longitude,
                    ).model_dump_json(),
                )

Callback Functions

main_fire.on_fire(method, properties, body)

Callback function parses a FireStarted message and switches FireState from “undefined” to “started”

    def on_fire(self, ch, method, properties, body):
        body = body.decode("utf-8")

        start = FireStarted.model_validate_json(body)
        for key, fire in self.fires.iterrows():
            if key == start.fireId:
                self.fires["fireState"][key] = FireState.started
                break
main_fire.on_detected(method, properties, body)

Callback function parses a FireDetected message, switches FireState from “started” to “detected”, and records time of first detection and name of satellite detecting the fire

    def on_detected(self, ch, method, properties, body):
        body = body.decode("utf-8")

        detect = FireDetected.model_validate_json(body)
        for key, fire in self.fires.iterrows():
            if key == detect.fireId:
                self.fires["fireState"][key] = FireState.detected
                self.fires["detected"][key] = detect.detected
                self.fires["detected_by"][key] = detect.detected_by
                break
main_fire.on_reported(method, properties, body)

Callback function parses a FireReported message, switches FireState from “detected” to “reported”, and records time of first report, name of satellite reporting the fire, and groundId receiving the report

    def on_reported(self, ch, method, properties, body):
        body = body.decode("utf-8")

        report = FireReported.model_validate_json(body)
        for key, fire in self.fires.iterrows():
            if key == report.fireId:
                self.fires["fireState"][key] = FireState.reported
                self.fires["reported"][key] = report.reported
                self.fires["reported_by"][key] = report.reported_by
                self.fires["reported_to"][key] = report.reported_to
                break

Schema

Schema are implemented using the pydantic library. The following schema define consistent message structures between this application and other observer applications:

pydantic settings FireStarted

Bases: BaseModel

Message schema object class with properties inherited from the pydantic library’s BaseModel

Standardized message for fire ignition includes:

Show JSON schema
{
   "title": "FireStarted",
   "description": "*Message schema object class with properties inherited from the pydantic library's BaseModel*\n\nStandardized message for fire ignition includes:",
   "type": "object",
   "properties": {
      "fireId": {
         "description": "Unique fire identifier.",
         "title": "Fireid",
         "type": "integer"
      },
      "start": {
         "anyOf": [
            {
               "format": "date-time",
               "type": "string"
            },
            {
               "type": "null"
            }
         ],
         "description": "Time fire started.",
         "title": "Start"
      },
      "latitude": {
         "anyOf": [
            {
               "maximum": 90,
               "minimum": -90,
               "type": "number"
            },
            {
               "type": "null"
            }
         ],
         "description": "Latitude (deg) of fire location.",
         "title": "Latitude"
      },
      "longitude": {
         "anyOf": [
            {
               "maximum": 180,
               "minimum": -180,
               "type": "number"
            },
            {
               "type": "null"
            }
         ],
         "description": "Longitude (deg) of fire location.",
         "title": "Longitude"
      }
   },
   "required": [
      "fireId",
      "start",
      "latitude",
      "longitude"
   ]
}

Fields:
  • fireId (int)

  • start (datetime.datetime | None)

  • latitude (float | None)

  • longitude (float | None)

field fireId: int [Required]

Unique fire identifier.

field start: datetime | None [Required]

Time fire started.

field latitude: float | None [Required]

Latitude (deg) of fire location.

Constraints:
  • ge = -90

  • le = 90

field longitude: float | None [Required]

Longitude (deg) of fire location.

Constraints:
  • ge = -180

  • le = 180

pydantic settings FireDetected

Bases: BaseModel

Message schema object class with properties inherited from the pydantic library’s BaseModel

Standardized message for fire detection includes:

Show JSON schema
{
   "title": "FireDetected",
   "description": "*Message schema object class with properties inherited from the pydantic library's BaseModel*\n\nStandardized message for fire detection includes:",
   "type": "object",
   "properties": {
      "fireId": {
         "description": "Unique fire identifier.",
         "title": "Fireid",
         "type": "integer"
      },
      "detected": {
         "description": "Time fire detected.",
         "format": "date-time",
         "title": "Detected",
         "type": "string"
      },
      "detected_by": {
         "description": "Satellite name that detected the fire.",
         "title": "Detected By",
         "type": "string"
      }
   },
   "required": [
      "fireId",
      "detected",
      "detected_by"
   ]
}

Fields:
  • fireId (int)

  • detected (datetime.datetime)

  • detected_by (str)

field fireId: int [Required]

Unique fire identifier.

field detected: datetime [Required]

Time fire detected.

field detected_by: str [Required]

Satellite name that detected the fire.

pydantic settings FireReported

Bases: BaseModel

Message schema object class with properties inherited from the pydantic library’s BaseModel

Standardized message for fire reporting includes:

Show JSON schema
{
   "title": "FireReported",
   "description": "*Message schema object class with properties inherited from the pydantic library's BaseModel*\n\nStandardized message for fire reporting includes:",
   "type": "object",
   "properties": {
      "fireId": {
         "description": "Unique fire identifier.",
         "title": "Fireid",
         "type": "integer"
      },
      "reported": {
         "description": "Time fire reported.",
         "format": "date-time",
         "title": "Reported",
         "type": "string"
      },
      "reported_by": {
         "description": "Satellite name that sent the fire report.",
         "title": "Reported By",
         "type": "string"
      },
      "reported_to": {
         "description": "Station id that received the fire report.",
         "title": "Reported To",
         "type": "integer"
      }
   },
   "required": [
      "fireId",
      "reported",
      "reported_by",
      "reported_to"
   ]
}

Fields:
  • fireId (int)

  • reported (datetime.datetime)

  • reported_by (str)

  • reported_to (int)

field fireId: int [Required]

Unique fire identifier.

field reported: datetime [Required]

Time fire reported.

field reported_by: str [Required]

Satellite name that sent the fire report.

field reported_to: int [Required]

Station id that received the fire report.


Startup Script

The following code demonstrates how the fires application is started up, how the Environment Observer object class is initialized and added to the simulator, and how the callback functions are added:

    # Define application name
    NAME = "fire"

    # Load config
    config = ConnectionConfig(yaml_file="firesat.yaml", app_name=NAME)

    # Create the managed application
    app = ManagedApplication(app_name=NAME)

    # Import CSV file from fire_scenarios subdirectory with scenario defining locations and ignition datetimes of fires
    csvFile = os.path.join("fires", "fire_scenarios", "first5days.csv")

    # Read the csv file and convert to a DataFrame with initial column defining the index
    df = pd.read_csv(csvFile, index_col=0)
    fires = pd.DataFrame(
        data={
            "fireId": df.index,
            "start": pd.to_datetime(df["start_time"], utc=True),
            "latitude": df["latitude"],
            "longitude": df["longitude"],
        }
    )

    # Add blank columns to data frame for logging state, detection time, reporting time, and detector satellite
    fires.insert(1, "fireState", FireState.undefined)
    fires.insert(3, "detected", datetime(1900, 1, 1, tzinfo=timezone.utc))
    fires.insert(4, "detected_by", "Undetected")
    fires.insert(5, "reported", datetime(1900, 1, 1, tzinfo=timezone.utc))
    fires.insert(6, "reported_by", "Unreported")
    fires.insert(7, "reported_to", None)

    # Add the environment observer to monitor for fire status events
    app.simulator.add_observer(Environment(app, fires))

    # Add a shutdown observer to shut down after a single test case
    app.simulator.add_observer(ShutDownObserver(app))

    # Start up the application
    app.start_up(
        config.rc.simulation_configuration.execution_parameters.general.prefix,
        config,
    )

    # Add message callbacks
    app.add_message_callback("fire", "location", on_fire)
    app.add_message_callback("constellation", "detected", on_detected)
    app.add_message_callback("constellation", "reported", on_reported)

    while True:
        pass