import { BaseOccupant } from "./base.mjs";
import { OccupantPhysiology, OccupantProduct } from "./occupant.mjs";
import { OccupantVersion } from "./occupant_version.mjs";
import { WorkflowOccupant } from "./workflow_occupant.mjs";
import { Structure } from "./structure.mjs";
export { ProtocolVehicle, VehicleOccupant };
class ProtocolVehicle {
/**
* Class to define a Vehicle with Occupants that can occupy each seat
* @param {VehicleOccupant[]} vehicle_occupants array of OccupantProduct names in driver seat
* @param {string[]} structures array of OccupantProduct names in driver seat
* @example
* let vehicle = new ProtocolVehicle(driver, front, behind_driver, behind_front, middle);
*/
constructor(vehicle_occupants, structures) {
this.vehicle_occupants = vehicle_occupants;
this.structures = structures;
}
/** vehicles occupants array
* @type {VehicleOccupant[]} */
get vehicle_occupants() {
return this._vehicle_occupants;
}
set vehicle_occupants(new_vehicle_occupants) {
this._vehicle_occupants = new_vehicle_occupants;
}
/** driver occupant
* @type {?VehicleOccupant} */
get driver() {
return this.GetOccupant(BaseOccupant.DRIVER);
}
/** front passenger occupant
* @type {?VehicleOccupant} */
get front() {
return this.GetOccupant(BaseOccupant.FRONT_PASSENGER);
}
/** behind driver occupant
* @type {?VehicleOccupant} */
get behind_driver() {
return this.GetOccupant(BaseOccupant.REAR_DRIVER_SIDE);
}
/** behind front occupant
* @type {?VehicleOccupant} */
get behind_front() {
return this.GetOccupant(BaseOccupant.REAR_PASSENGER_SIDE);
}
/** rear middle passenger occupant names array
* @type {?VehicleOccupant} */
get middle() {
return this.GetOccupant(BaseOccupant.REAR_MIDDLE);
}
/**
* Array of Structure types
* @type {string[]}
*/
get structures() {
return this._structures;
}
set structures(new_structures) {
if (!(new_structures instanceof Array)) {
throw new Error("structures must be an Array");
}
for (let new_structure of new_structures) {
// @ts-ignore
if (!Structure.Types().includes(new_structure)) {
throw new Error(`${new_structure} is not a valid Structure type - check the spelling and case`);
}
}
this._structures = new_structures;
}
/** driver occupant names array
* @param {string} position
* @return {?VehicleOccupant}
*/
GetOccupant(position) {
for (var occupant of this.vehicle_occupants) {
if (occupant.position == position) {
return occupant;
}
}
//if we get here then the occupant doesn't yet exist so add an empty occupant
let empty_occupant = new VehicleOccupant(position);
this.vehicle_occupants.push(empty_occupant);
// Message(`Making empty ${position} occupant ${JSON.stringify(empty_occupant.ToJSON())}`);
return empty_occupant;
}
/**
* VehicleOccupant-like object
* @typedef {Object} VehicleOccupantJSON
* @property {string} position Occupant position
* @property {string} product Occupant product type
* @property {string} physiology Occupant physiology
*/
/**
* an array of VehicleOccupant-like objects
* @typedef {Object} ProtocolVehicleJSON
* @property {VehicleOccupantJSON[]} occupants
* @property {string[]} structures
*/
/**
* construct a ProtocolVehicle from JSON
* @param {ProtocolVehicleJSON} json
* @return {ProtocolVehicle}
*/
static FromJSON(json) {
let expected_keys = ["occupants"];
for (let key of expected_keys) {
if (!(key in json)) {
throw new Error(`${key} is not a key in the json object passed to ProtocolVehicle.FromJSON(json)`);
}
}
/**
* set undefined optional keys to empty array
* NOTE: structures property is optional so may not be defined. If it is not defined set as empty array
*/
let optional_keys = ["structures"];
for (let key of optional_keys) {
if (!(key in json)) {
json[key] = [];
}
}
let occupants = [];
for (let vo_json of json.occupants) {
try {
let vo = VehicleOccupant.FromJSON(vo_json);
if (vo.Empty()) Message(`Skipping ${vo.position}`);
occupants.push(vo);
} catch (error) {
WarningMessage(
`Failed to construct a vehicle occupant. Check the protocol vehicle json was correctly defined`
);
}
}
return new ProtocolVehicle(occupants, json.structures);
}
/**
* return JSON representation of ProtocolVehicle with protocol data
* @param {string} regulation Regulation, e.g. Regulation.CNCAP
* @param {string} crash_test Crash test, e.g. CrashTest.ODB
* @param {string} version Version, e.g. "2017"
* @param {?string} [description=null] e.g. "2017"
* @param {?string} [drive_side=null]
* @return {Object}
*/
ToJSON(regulation, crash_test, version, description = null, drive_side = null) {
let json = {};
let vehicle = { occupants: [], structures: [] };
if (description == null) description = `protocol based on... ${version}`;
for (let o of this.Occupants()) {
//don't write out empty occupants
if (o.Empty()) continue;
let json_occ = o.ToJSON();
Message(JSON.stringify(json_occ));
vehicle.occupants.push(json_occ);
}
// copy structures
for (let s of this.structures) {
vehicle.structures.push(s);
}
json.regulation = regulation;
json.crash_test = crash_test;
json.version = version.substring(0, 4); //just save year
json.description = description;
if (drive_side) json.drive_side = drive_side;
json.vehicle = vehicle;
return json;
}
/**
* get array of all the VehicleOccupants in the ProtocolVehicle
* @returns {VehicleOccupant[]}
* @example
* let occupants = this.Occupants();
*/
Occupants() {
let vehicle = this;
return [vehicle.driver, vehicle.behind_driver, vehicle.front, vehicle.behind_front, vehicle.middle];
}
/**
* get array of all the VehicleOccupants in the ProtocolVehicle
*/
/**
* returns an array of valid (non-empty) VehicleOccupants that match all the passed criteria for position
* side and front_rear.
* Note that each position is unique so passing additonal arguments may result in an empty array being returned
* Note that null can be passed for any of these criteria and it will not filter on that property
* Note also that zero length array is returned if no matching occupants found
* @param {?string} [position = null] Workflowoccupant.DRIVER or WorkflowOccupant.PASSENGER
* @param {?string} [front_rear = null] front or rear row seat
* @param {?string} [side = null] WorkflowOccupant.LEFT, WorkflowOccupant.RIGHT or WorkflowOccupant.MIDDLE
* @param {string} [drive_side = VehicleOccupant.LHD] VehicleOccupant.LHD by default, but can set to VehicleOccupant.RHD. This is only used if side is not null
* @returns {VehicleOccupant[]}
* @example
* let occupants = this.OnlyOccupants(WorkflowOccupant.PASSENGER, WorkflowOccupant.REAR);
*/
OnlyOccupants(position = null, front_rear = null, side = null, drive_side = VehicleOccupant.LHD) {
let occupants = this.Occupants();
let output_occupants = [];
for (let occupant of occupants) {
if (!occupant.Empty()) {
if (position != null) {
if (occupant.position != position) {
continue;
}
}
if (front_rear != null) {
if (occupant.GetRow() != front_rear) {
continue;
}
}
if (side != null) {
if (occupant.GetSide(drive_side) != side) {
continue;
}
}
output_occupants.push(occupant);
}
}
return output_occupants;
}
}
class VehicleOccupant extends BaseOccupant {
/**
* this class holds vehicle occupant data from the protocol json file
* it also adds meta data e.g. side and position. Positions assume
* left-hand drive (LHD) by default, but GetPosition method can be used
* to return the correct positions if vehicle is right-hand drive (RHD).
*/
/**
*
* @param {string} position
* @param {?string} [product = null] taken from JSON
* @param {?string} [physiology = null] taken from JSON
* @example let driver = new VehicleOccupant(
WorkflowOccupant.DRIVER,
WorkflowOccupant.LEFT,
WorkflowOccupant.FRONT,
json.driver.product,
json.driver.physiology
)
*/
constructor(position, product = null, physiology = null) {
super(position);
this.product = product;
this.physiology = physiology;
}
/** Left-hand drive (LHD) constant
* @type {string} */
static get LHD() {
return "LHD";
}
/** Right-hand drive (RHD) constant
* @type {string} */
static get RHD() {
return "RHD";
}
/**
* if the hand drive is valid returns the value passed, otherwise prints warning and return VehicleOccupant.LHD
* @param {string} drive_side
* @returns {string}
*/
static GetValidHandDrive(drive_side) {
if (drive_side == VehicleOccupant.LHD || drive_side == VehicleOccupant.RHD) return drive_side;
WarningMessage(
`Vehicle hand drive ${drive_side} was not valid so cannot be set. Defaulting to ${VehicleOccupant.LHD}`
);
return VehicleOccupant.LHD;
}
/**
* make the occupant empty by setting product and physiology to null
*/
SetEmpty() {
this.product = null;
this.physiology = null;
}
/**
* occupant may be empty if either product or physiology are null
* @returns {boolean} empty
*/
Empty() {
if (this.position && this.physiology) return false;
return true;
}
/** Occupant product
* @type {?string|undefined} */
get product() {
return this._product;
}
set product(new_product) {
if (!new_product) new_product = null;
else if (!OccupantProduct.Valid(new_product)) {
throw new Error(`Invalid product ${new_product} in VehicleOccupant constructor`);
}
this._product = new_product;
}
/** Occupant physiology
* @type {?string|undefined} */
get physiology() {
return this._physiology;
}
set physiology(new_physiology) {
if (!new_physiology) new_physiology = null;
else if (!OccupantPhysiology.Valid(new_physiology)) {
throw new Error(`Invalid physiology ${new_physiology} in VehicleOccupant constructor`);
}
this._physiology = new_physiology;
}
/**
* returns a VehicleOccupant from the json
* @param {VehicleOccupantJSON} json
* @returns
*/
static FromJSON(json) {
return new VehicleOccupant(json.position, json.product, json.physiology);
}
/**
* returns an object representation of VehicleOccupant
* @returns {Object}
*/
ToJSON() {
let json = {};
json.position = this.position;
json.product = this.product;
json.physiology = this.physiology;
return json;
}
/**
* Get the side of an occupant for a given drive side (LHD or RHD) based on position
* @param {string} [drive_side = VehicleOccupant.LHD] VehicleOccupant.LHD (default) or VehicleOccupant.RHD
* @returns {string}
*/
GetSide(drive_side = VehicleOccupant.LHD) {
if (this.position == WorkflowOccupant.REAR_MIDDLE)
// Middle is always middle regardless of drive_side
return WorkflowOccupant.MIDDLE;
switch (drive_side) {
case VehicleOccupant.LHD:
switch (this.position) {
case WorkflowOccupant.DRIVER:
case WorkflowOccupant.REAR_DRIVER_SIDE:
return WorkflowOccupant.LEFT;
case WorkflowOccupant.FRONT_PASSENGER:
case WorkflowOccupant.REAR_PASSENGER_SIDE:
return WorkflowOccupant.RIGHT;
}
case VehicleOccupant.RHD:
switch (this.position) {
case WorkflowOccupant.DRIVER:
case WorkflowOccupant.REAR_DRIVER_SIDE:
return WorkflowOccupant.RIGHT;
case WorkflowOccupant.FRONT_PASSENGER:
case WorkflowOccupant.REAR_PASSENGER_SIDE:
return WorkflowOccupant.LEFT;
}
}
}
/**
* returns an array of valid (supported) occupant names corresponding to
* the occupant product and physiology properties
* @returns {string[]} occupant names array
*/
GetOccupantNames() {
return OccupantVersion.GetOnly("ALL", this.product, this.physiology);
}
}