import proxies from "./../../data/datasources/proxy.js";
import { retrieve } from "./../../data/data.js";
import {
Layers,
renderMap,
recenter,
addCustomLegend,
} from "./../../map/map.js";
/**
* This class contains methods for modeling various flood damage and mitigation scenarios for hydrological analyses.
* Provides comprehensive flood impact assessment including property damage, vehicle damage, infrastructure damage,
* life loss estimation, and mitigation planning capabilities for multiple cities.
*
* @class
* @name floodDM
* @example
* // Initialize a new flood damage and mitigation scenario
* const scenario = new floodDM();
*
* // Run a damage scenario for Cedar Falls with 18ft depth, 500-year flood
* const damageResults = await scenario.initDamageScenario({
* params: { maptype: 'leaflet' },
* args: { city: "Cedar Falls", depth: 18, scenario: '500-year' }
* });
*
* // Run a mitigation scenario for Waterloo
* const mitigationResults = await scenario.initMitigationScenario({
* params: { maptype: 'leaflet' },
* args: { city: "Waterloo", depth: 30.5 }
* });
*/
export default class floodDM {
city = "";
depth = 0;
mapRendered = false;
selectedfeature = null;
/**
* Initialize a comprehensive damage scenario for a particular city with specified flood parameters.
* Calculates and visualizes damage to buildings, vehicles, bridges, and utilities while estimating
* life loss and debris generation. Renders an interactive map with damage layers and summary statistics.
*
* @method initDamageScenario
* @memberof floodDM
* @instance
* @async
* @param {Object} options - Configuration object for damage scenario
* @param {Object} [options.params] - Parameters for map rendering and visualization
* @param {string} [options.params.maptype='leaflet'] - Type of map to render: 'google' or 'leaflet'
* @param {string} [options.params.key] - Google Maps API key (required if maptype is 'google')
* @param {Object} options.args - Arguments defining the flood scenario
* @param {string} options.args.city - City name. Supported: 'Bettendorf', 'Cedar Falls', 'Cedar Rapids', 'Davenport', 'Iowa City', 'Waterloo', 'Waverly'
* @param {number} options.args.depth - Flood depth in feet
* @param {string} options.args.scenario - Flood return period: '100-year' or '500-year'
* @param {Object} [options.data] - Additional data (currently unused)
* @returns {Promise<Object>} Promise resolving to comprehensive damage assessment results
*
* @example
* // Initialize damage scenario for Cedar Falls with 500-year flood at 18ft depth
* const scenario = new floodDM();
* const damageResults = await scenario.initDamageScenario({
* params: {
* maptype: 'leaflet' // or 'google' with API key
* },
* args: {
* city: "Cedar Falls",
* depth: 18,
* scenario: '500-year'
* }
* });
*
* // Results include damage categories:
* console.log(damageResults.Buildings.Structure); // "$2,450,000"
* console.log(damageResults.Vehicles.Day); // "$1,200,000"
* console.log(damageResults.Bridges.Damage); // "$850,000"
* console.log(damageResults.Utilities.Damage); // "$340,000"
*
* @example
* // Initialize with Google Maps
* const damageResults = await scenario.initDamageScenario({
* params: {
* maptype: 'google',
* key: 'your-google-maps-api-key'
* },
* args: {
* city: "Waterloo",
* depth: 25,
* scenario: '100-year'
* }
* });
*
* @example
* // View specific damage components
* const results = await scenario.initDamageScenario({
* params: { maptype: 'leaflet' },
* args: { city: "Davenport", depth: 22, scenario: '500-year' }
* });
*
* // Building damage breakdown
* console.log(results.Buildings.Structure); // Structural damage
* console.log(results.Buildings.Content); // Content damage
* console.log(results.Buildings.Income); // Income loss
* console.log(results.Buildings.Wage); // Wage loss
* console.log(results.Buildings["Loss of Life (Day)"]); // Fatalities during day
* console.log(results.Buildings.Debris); // Debris amount (tons)
*/
async initDamageScenario({ params, args, data } = {}) {
// Destructure the arguments passed to the function
let { city, depth, scenario } = args;
this.city = city;
this.depth = depth;
// Set default map type as leaflet
if (params === undefined) {
this.maptype = "leaflet";
params = {};
} else if (params.maptype === undefined) {
this.maptype = "leaflet";
} else {
this.maptype = params.maptype;
}
// Check if key is present if google map is rendered
if (this.maptype === "google") {
if (params.key === undefined) {
console.log("param.key value is required to render a google map");
return;
}
}
// Normalize the scenario argument to a specific format
if (scenario === "100-year") scenario = "x100_year";
else if (scenario === "500-year") scenario = "x500_year";
else {
console.log("Pass 100-year or 500-year flood scenario in the args.scenario");
return;
}
// Set class member as 100 or 500 year scenario
this.scenario = scenario;
// Retrieve the flood layer based on the selected scenario
let flood_layer = await retrieve({
params: {
source: "flooddamage_dt",
datatype: scenario,
transform: "eval",
},
args: { sourceType: this.city },
});
// Retrieve the utilities layer based on the selected scenario
let utilities_layer;
// Utilities Layer data source is avilable for Cedar Falls only
if (this.city === "Cedar Falls") {
utilities_layer = await retrieve({
params: {
source: "flooddamage_dt",
datatype: "utilities",
transform: "eval",
},
args: { sourceType: this.city },
});
}
// Retrieve the vehicles layer based on the selected scenario
let vehicles_layer = await retrieve({
params: {
source: "flooddamage_dt",
datatype: "vehicles",
transform: "eval",
},
args: { sourceType: this.city },
});
// Retrieve the buildings layer based on the selected scenario
let buildings_layer = await retrieve({
params: {
source: "flooddamage_dt",
datatype: "buildings",
transform: "eval",
},
args: { sourceType: this.city },
});
// Retrieve the bridge layer based on the selected scenario
let bridges_layer;
// Bridge Layer data source only exists for Cedar Falls, Cedar Rapids, Davenport, Waverly
if (
this.city === "Cedar Falls" ||
this.city === "Cedar Rapids" ||
this.city === "Davenport" ||
this.city === "Waverly"
)
bridges_layer = await retrieve({
params: {
source: "flooddamage_dt",
datatype: "bridges",
transform: "eval",
},
args: { sourceType: this.city },
});
// Calculate the centroid of the buildings layer so render map center
let centroid = floodDM.featureCollectionCentroid(buildings_layer);
// Render the map if it hasn't been rendered before
if (!this.mapRendered) {
await renderMap({
params: { maptype: this.maptype, lat: centroid[1], lon: centroid[0] },
args: { key: params.key },
});
this.mapRendered = true;
}
// Variable to store and render scenario rendering results
let scenario_results = {
Buildings: {
Structure: 0,
Content: 0,
Income: 0,
Wage: 0,
"Relocation Expenses": 0,
"Rental Income": 0,
"Loss of Life (Day)": 0,
"Loss of Life (Night)": 0,
Debris: 0,
},
Vehicles: {
Day: 0,
Night: 0,
},
Bridges: {
Damage: 0,
},
Utilities: {
Damage: 0,
},
};
/*
* Begin Rendering Vehicles Layer
*/
// Variable to store vehicle layer calculations
let vehicleFeatureCalculations = {};
// Retrieve the vehicle damage function data from damage datasource
let vehicleDamFun = await floodDM.getVehicleDamageFunction();
// Render the vehicle damage layer
await Layers({
args: {
type: "geojson",
name: "Vehicles Layer",
// Set feature style using feature properties
styleFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
// Calculate vehicle damage based on flood depth and area
let depth100yr = properties.flood100_avg;
let depth500yr = properties.flood500_avg;
let areaPrc100yr = properties.area_fe_100yr;
let areaPrc500yr = properties.area_fe_500yr;
let car = "PassengerCar";
let light = "LightTruck";
let heavy = "HeavyTruck";
let inundationDepth,
areaPrc,
vehDamPrcntCar,
vehDamPrcntLight,
vehDamPrcntHeavy;
if (this.scenario === "x100_year") {
inundationDepth = depth100yr;
areaPrc = areaPrc100yr;
} else if (this.scenario === "x500_year") {
inundationDepth = depth500yr;
areaPrc = areaPrc500yr;
}
for (let i in vehicleDamFun[car]) {
if (vehicleDamFun[car][i]["floodDepth"] == inundationDepth) {
vehDamPrcntCar = vehicleDamFun[car][i]["damage"];
}
}
for (let i in vehicleDamFun[light]) {
if (vehicleDamFun[light][i]["floodDepth"] == inundationDepth) {
vehDamPrcntLight = vehicleDamFun[light][i]["damage"];
}
}
for (let i in vehicleDamFun[heavy]) {
if (vehicleDamFun[heavy][i]["floodDepth"] == inundationDepth) {
vehDamPrcntHeavy = vehicleDamFun[heavy][i]["damage"];
}
}
// Calculate vehicle damage for different vehicle types at day and night
let carDamageD = parseInt(
(vehDamPrcntCar / 100) * areaPrc * properties.car_val_day
).toLocaleString();
let lightDamageD = parseInt(
(vehDamPrcntLight / 100) * areaPrc * properties.light_val_day
).toLocaleString();
let heavyDamageD = parseInt(
(vehDamPrcntHeavy / 100) * areaPrc * properties.heavy_val_day
).toLocaleString();
let carDamageN = parseInt(
(vehDamPrcntCar / 100) * areaPrc * properties.car_val_night
).toLocaleString();
let lightDamageN = parseInt(
(vehDamPrcntLight / 100) * areaPrc * properties.light_val_night
).toLocaleString();
let heavyDamageN = parseInt(
(vehDamPrcntHeavy / 100) * areaPrc * properties.heavy_val_night
).toLocaleString();
let carDamageDC =
(vehDamPrcntCar / 100) * areaPrc * properties.car_val_day;
let lightDamageDC =
(vehDamPrcntLight / 100) * areaPrc * properties.light_val_day;
let heavyDamageDC =
(vehDamPrcntHeavy / 100) * areaPrc * properties.heavy_val_day;
let carDamageNC =
(vehDamPrcntCar / 100) * areaPrc * properties.car_val_night;
let lightDamageNC =
(vehDamPrcntLight / 100) * areaPrc * properties.light_val_night;
let heavyDamageNC =
(vehDamPrcntHeavy / 100) * areaPrc * properties.heavy_val_night;
let totalDamageD = carDamageDC + lightDamageDC + heavyDamageDC;
let totalDamageN = carDamageNC + lightDamageNC + heavyDamageNC;
// Update scenario variable
if (!isNaN(totalDamageD))
scenario_results["Vehicles"]["Day"] += totalDamageD;
if (!isNaN(totalDamageN))
scenario_results["Vehicles"]["Night"] += totalDamageN;
// Update vehicle damage calculations for each feature
vehicleFeatureCalculations[properties.CensusBloc] = {
"Block Information": properties.CensusBloc,
"Flood Scenario:": this.scenario,
"Avg. Flood Depth:": inundationDepth,
"Car Count (Day):": properties.cars_day,
"Car Count (Night):": properties.cars_night,
"Light Truck Count (Day)": properties.light_day,
"Light Truck Count (Night)": properties.light_night,
"Heavy Truck Count (Day)": properties.heavy_day,
"Heavy Truck Count (Night)": properties.heavy_night,
"Cars Damage (Day)": carDamageD,
"Cars Damage (Night)": carDamageN,
"Light Trucks (Day)": lightDamageD,
"Light Trucks (Night)": lightDamageN,
"Heavy Trucks (Day)": heavyDamageD,
"Heavy Truck (Night)": heavyDamageN,
};
// return style values for the feature
return {
fillColor:
totalDamageD > 1000000
? "#323232"
: totalDamageD > 100000
? "#7E7E7E"
: "#BFBFBF",
weight: 2,
opacity: 1,
color: "black",
dashArray: "3",
fillOpacity: 0.9,
};
},
// Set popup values based on feature properties
popUpFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
// return popup values for this feature
return JSON.stringify(
vehicleFeatureCalculations[properties.CensusBloc]
).replaceAll(",", "<br>").replaceAll(/|{|}/g,"");
},
},
data: vehicles_layer,
});
/*
* Begin Rendering Flood Inundation Layer
*/
// Render flood inundation layer
await Layers({
args: {
type: "geojson",
name: "Flood Inundation Layer",
// Set style based on feature properties
styleFunction: (feature) => {
// Return style options for this feature
return {
color: "blue",
weight: 1,
opacity: 1,
fillColor: "blue",
fillOpacity: 0.5,
};
},
},
data: flood_layer,
});
/*
* Begin Rendering Vehicles Layer
*/
// Variable to store building layer calculations
let buildingFeatureCalculations = [];
// Get building, life and debris weight functions
let buildingDamage = await floodDM.getPropertyDamageFunction();
let lifeLoss = await floodDM.getLifeLossFunction();
let debrisLoss = await floodDM.getDebrisWeightFunction();
// Render Building Layer Calculations
await Layers({
args: {
type: "geojson",
name: "Properties Layer",
// Set style based on feature properties
styleFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
// Calculate metrics for property damage
let occupancy = properties.occupancy;
let inundationDepth =
scenario === "x100_year"
? properties.depth100
: properties.depth500;
let propertyInputs = buildingDamage[occupancy].find(
(item) => item.floodDepth === inundationDepth
);
let structureDamPercent = propertyInputs["structure"];
let contentDamPercent = propertyInputs["content"];
let incomeRecaptureFactor = propertyInputs["recapturFactor"];
let incomePerDay = propertyInputs["incomePerSqftPerDay"];
let lossFunction = propertyInputs["maxTime"];
let wageRecaptureFactor = propertyInputs["recapturFactor"];
let wagePerDay = propertyInputs["wagePerSqftPerDay"];
let percentOwnerOccupied = propertyInputs["percentOwnerOccupied"];
let rentalCostPerSqftPerDay =
propertyInputs["rentalCostPerSqftPerDay"];
let disruptionCostPerSqft = propertyInputs["disruptionCostPerSqft"];
let relocation, rentalIncome;
if (propertyInputs["structure"] > 10) {
relocation = [
(1 - percentOwnerOccupied / 100) * disruptionCostPerSqft +
(percentOwnerOccupied / 100) *
(disruptionCostPerSqft +
rentalCostPerSqftPerDay * lossFunction * 30),
];
rentalIncome =
(1 - percentOwnerOccupied / 100) *
rentalCostPerSqftPerDay *
lossFunction *
30;
} else {
relocation = 0;
rentalIncome = 0;
}
let strDamage = (properties.val_struct * structureDamPercent) / 100;
let contDamage = (properties.val_cont * contentDamPercent) / 100;
let incomeLoss =
(1 - incomeRecaptureFactor) *
properties.sqfoot *
incomePerDay *
lossFunction *
30;
let wageLoss =
(1 - wageRecaptureFactor) *
properties.sqfoot *
wagePerDay *
lossFunction *
30;
let relocationExpenses = properties.sqfoot * relocation;
let rentalIncomeLoss = properties.sqfoot * rentalIncome;
let totalDamageBuild =
strDamage +
contDamage +
incomeLoss +
wageLoss +
relocationExpenses +
rentalIncomeLoss;
// Update scenario variable with building damage
if (!isNaN(strDamage))
scenario_results.Buildings.Structure += strDamage;
if (!isNaN(contDamage))
scenario_results.Buildings.Content += contDamage;
if (!isNaN(incomeLoss))
scenario_results.Buildings.Income += incomeLoss;
if (!isNaN(wageLoss)) scenario_results.Buildings.Wage += wageLoss;
if (!isNaN(relocationExpenses))
scenario_results.Buildings["Relocation Expenses"] +=
relocationExpenses;
if (!isNaN(rentalIncomeLoss))
scenario_results.Buildings["Rental Income"] += rentalIncomeLoss;
// Calculate metrics for life damage
// Filter life function data using inputs
let lifeInputsOver65 = lifeLoss[occupancy].find((item) => {
return (
item.floodDepth === inundationDepth && item.age === "over 65"
);
});
let lifeInputsUnder65 = lifeLoss[occupancy].find((item) => {
return (
item.floodDepth === inundationDepth && item.age === "under 65"
);
});
let lifeLossDay =
lifeInputsOver65["zone"] * properties.POP_O65DAY +
lifeInputsUnder65["zone"] * properties.POP_U65DAY;
let lifeLossNight =
lifeInputsOver65["zone"] * properties.POP_O65NGT +
lifeInputsUnder65["zone"] * properties.POP_U65NGT;
// Update scenario variable with life damage
if (!isNaN(lifeLossDay))
scenario_results.Buildings["Loss of Life (Day)"] += lifeLossDay;
if (!isNaN(lifeLossNight))
scenario_results.Buildings["Loss of Life (Night)"] += lifeLossNight;
// Calculate metrics for debris damage
// Filter debris function data using inputs
let debrisValue = debrisLoss[occupancy].find((item) => {
return (
item["floodDepth"] == inundationDepth &&
item["foundationType"] == "footing"
);
});
let finishes = debrisValue["finishes"];
let structure = debrisValue["structure"];
let foundation = debrisValue["foundation"];
let debrisWeight = finishes + structure + foundation;
let debrisAmount = (properties.sqfoot * debrisWeight) / 1000;
// Update scenario variable with debris damage
if (!isNaN(debrisAmount))
scenario_results.Buildings.Debris += debrisAmount;
// Store building feature calculations for reuse in popup function
buildingFeatureCalculations.push({
occupancy,
gid: properties.gid,
totalDamageBuild,
strDamage,
contDamage,
incomeLoss,
wageLoss,
relocationExpenses,
rentalIncomeLoss,
debrisAmount,
lifeLossDay,
lifeLossNight,
inundationDepth,
});
// Hide building marker if it is not underwater under the current scenario
if (scenario === "x100_year") {
if (properties.depth100 < 1)
return { display: "none", color: "none", fillColor: "none" };
} else {
if (properties.depth500 < 1)
return { display: "none", color: "none", fillColor: "none" };
}
// Return styling of the building marker later
return {
fillColor:
totalDamageBuild > 1000000
? "red"
: totalDamageBuild > 100000
? "yellow"
: "green",
weight: 2,
opacity: 1,
color: "black",
fillOpacity: 1,
radius: 4,
scale: 4,
strokeWeight: 0.7,
};
},
// Return the popup value based on feature properties
popUpFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
// Return value to rendered in the popup of the building
let val = JSON.stringify(
buildingFeatureCalculations.find(
(item) => item.gid === properties.gid
)
);
return val.replaceAll(",", "<br>").replaceAll(/|{|}/g,"");
},
},
data: buildings_layer,
});
/*
* Begin Rendering Vehicle Layer
*/
// Bridges Layer only exists for Cedar Falls, Cedar Rapids, Davenport and Waverly
if (
this.city === "Cedar Falls" ||
this.city === "Cedar Rapids" ||
this.city === "Davenport" ||
this.city === "Waverly"
) {
// Get bridge damage functions data
let bridgeDamFun = await floodDM.getBridgeDamageFunction();
await Layers({
args: {
type: "geojson",
name: "Bridge Layer",
// Set bridge style function
styleFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
// Calculate bridge damage
let floodScenario, bridgDam, inundationDepth, bridgeDamPrcnt;
let bridgeT = properties.bridgeT;
let scour = String(properties.ScourIndex);
let bridgeCost = properties.Cost;
let depth100 = properties.flood100;
let depth500 = properties.flood500;
let depth100yr = "100 yr";
let depth500yr = "500 yr";
if (scenario === "x100_year") {
floodScenario = "100-year";
} else {
floodScenario = "500-year";
}
if (floodScenario === "100-yr" && depth100 === "yes") {
inundationDepth = depth100yr;
} else if (floodScenario === "500-yr") {
inundationDepth = depth500yr;
} else {
bridgDam = 0;
}
for (let i in bridgeDamFun[bridgeT]) {
if (
bridgeDamFun[bridgeT][i]["ScourPotential"] == scour &&
bridgeDamFun[bridgeT][i]["Flood Scenario"] == inundationDepth
) {
bridgeDamPrcnt = bridgeDamFun[bridgeT][i]["damage prcnt"];
}
}
bridgDam = bridgeDamPrcnt * bridgeCost * 1000;
// Update scenario variable
if (!isNaN(bridgDam))
scenario_results["Bridges"]["Damage"] += bridgDam;
// Return styling
return {
scale: 5,
radius: 2,
weight: 10,
fillColor: "black",
color: "black",
fillOpacity: 0.5,
strokeWeight: 0.7,
};
},
popUpFunction: (feature) => {
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
console.log(feature);
let totalBridgDam = 0,
floodScenario,
bridgDam,
inundationDepth,
bridgeDamPrcnt,
bridgeFunction;
let bridgeT = properties.bridgeT;
let scour = String(properties.ScourIndex);
let bridgeCost = properties.Cost;
let depth100 = properties.flood100;
let depth500 = properties.flood500;
let depth100yr = "100 yr";
let depth500yr = "500 yr";
if (scenario === "x100_year") {
floodScenario = "100-year";
} else {
floodScenario = "500-year";
}
if (floodScenario === "100-yr" && depth100 === "yes") {
inundationDepth = depth100yr;
} else if (floodScenario === "500-yr") {
inundationDepth = depth500yr;
} else {
bridgDam = 0;
}
for (let i in bridgeDamFun[bridgeT]) {
if (
bridgeDamFun[bridgeT][i]["ScourPotential"] == scour &&
bridgeDamFun[bridgeT][i]["Flood Scenario"] == inundationDepth
) {
bridgeDamPrcnt = bridgeDamFun[bridgeT][i]["damage prcnt"];
bridgeFunction = bridgeDamFun[bridgeT][i]["functional"];
}
}
bridgDam = bridgeDamPrcnt * bridgeCost * 1000;
let val = JSON.stringify({
Bridge_ID: properties.HighwayBri,
Bridge_Type: bridgeT,
Scour_Index: scour,
Flood_Scenario: inundationDepth,
Damage: bridgeDamPrcnt * 100,
Damage: parseInt(bridgDam).toLocaleString(),
Functionality: parseInt(bridgeFunction * 100),
});
return val.replaceAll(",", "<br>").replaceAll(/|{|}/g,"");
},
},
data: bridges_layer,
});
}
/*
* Begin Rendering Utilities Layer
*/
// Utility data exists for Cedar Falls only
if (this.city === "Cedar Falls") {
// Get utilities damage functions data
let utilityDamFun = await floodDM.getUtilityDamageFunction();
await Layers({
args: {
type: "geojson",
name: "Utilities Layer",
// Function to set style
styleFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
let utilityDam = 0,
inundationDepth,
utilityType,
utilityDamPrcnt;
let depth100 = properties.depth100;
let depth500 = properties.depth500;
let utilitySystem = properties.UtilFcltyC;
let utilityCost = properties.Cost;
let floodScenario = this.scenario;
if (floodScenario === "x100_year") {
inundationDepth = depth100;
} else if (floodScenario === "x500_year") {
inundationDepth = depth500;
}
for (let i in utilityDamFun[utilitySystem]) {
if (
utilityDamFun[utilitySystem][i]["floodDepth"] ===
inundationDepth
) {
utilityDamPrcnt = utilityDamFun[utilitySystem][i]["damage"];
utilityType = utilityDamFun[utilitySystem][i]["utilitySystem"];
}
}
utilityDam = (utilityDamPrcnt / 100) * utilityCost * 1000;
//Update scenario variable
if (!isNaN(utilityDam))
scenario_results["Utilities"]["Damage"] += utilityDam;
return {
scale: 2,
radius: 2,
weight: 10,
fillColor: "#F2B679",
color: "#F2B679",
fillOpacity: 0.5,
strokeWeight: 0.7,
};
},
// Set popup properties based on feature properties
popUpFunction: (feature) => {
// Destructure properties according to features based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
// Calculate utilities values
let utilityDam = 0,
inundationDepth,
utilityType,
utilityDamPrcnt;
let depth100 = properties.depth100;
let depth500 = properties.depth500;
let utilitySystem = properties.UtilFcltyC;
let utilityCost = properties.Cost;
let floodScenario = this.scenario;
if (floodScenario === "x100_year") {
inundationDepth = depth100;
} else if (floodScenario === "x500_year") {
inundationDepth = depth500;
}
for (let i in utilityDamFun[utilitySystem]) {
if (
utilityDamFun[utilitySystem][i]["floodDepth"] ===
inundationDepth
) {
utilityDamPrcnt = utilityDamFun[utilitySystem][i]["damage"];
utilityType = utilityDamFun[utilitySystem][i]["utilitySystem"];
}
}
utilityDam = (utilityDamPrcnt / 100) * utilityCost * 1000;
// Set popup values
return JSON.stringify({
"Utiltiy ID": properties.WasteWater,
"Utility Type": utilityType,
"Flood depth": inundationDepth,
Damage: parseInt(utilityDam),
}).replaceAll(",", "<br>").replaceAll(/|{|}/g,"");
},
},
data: utilities_layer,
});
}
/*
* Create and display a legend on the map
*/
// Create legend div for the map
const legendHTML = `<div id="legend" class="content"
style="display: block; background: rgba(255, 255, 255, 1); padding: 2px 5px; border-style: solid !important;">
<b>Legend</b>
<br>
<span>●</span> Bridge
<br>
<span style="color: orange;">●</span> Utility
<br>
<span style="background-color: blue; color: blue;">●</span> Flood extent
<br>
<b>Building Damage ($)</b>
<br>
<span style="color: green;">●</span> 0 - 100k
<br>
<span style="color: yellow;">●</span> 100k - 1M
<br>
<span style="color: red;">●</span> 1M+
<br>
<b>Vehicle Damage ($)</b>
<br>
<span style="background-color: #BFBFBF; color: #BFBFBF;">●</span> 0 - 100k
<br>
<span style="background-color: #7E7E7E; color: #7E7E7E;">●</span> 100k - 1M
<br>
<span style="background-color: #323232; color: #323232;">●</span> 1M+
</div>`;
// Call the addCustomLegend function for Maps
const legendDiv = document.createElement("div");
legendDiv.innerHTML = legendHTML;
await addCustomLegend({
params: {
position: "bottom left",
},
args: {
div: legendDiv,
},
});
/*
* Create and display a overlay to display scenario summary on the map
*/
// Format results before printing
scenario_results["Vehicles"]["Day"] = floodDM.formatUSDollar(
scenario_results["Vehicles"]["Day"]
);
scenario_results["Vehicles"]["Night"] = floodDM.formatUSDollar(
scenario_results["Vehicles"]["Night"]
);
scenario_results["Buildings"]["Structure"] = floodDM.formatUSDollar(
scenario_results["Buildings"]["Structure"]
);
scenario_results["Buildings"]["Content"] = floodDM.formatUSDollar(
scenario_results["Buildings"]["Content"]
);
scenario_results["Buildings"]["Income"] = floodDM.formatUSDollar(
scenario_results["Buildings"]["Income"]
);
scenario_results["Buildings"]["Wage"] = floodDM.formatUSDollar(
scenario_results["Buildings"]["Wage"]
);
scenario_results["Buildings"]["Relocation Expenses"] =
floodDM.formatUSDollar(
scenario_results["Buildings"]["Relocation Expenses"]
);
scenario_results["Buildings"]["Rental Income"] = floodDM.formatUSDollar(
scenario_results["Buildings"]["Rental Income"]
);
scenario_results["Utilities"]["Damage"] = floodDM.formatUSDollar(
scenario_results["Utilities"]["Damage"]
);
scenario_results["Buildings"]["Loss of Life (Day)"] = Math.floor(
scenario_results["Buildings"]["Loss of Life (Day)"]
);
scenario_results["Buildings"]["Loss of Life (Night)"] = Math.floor(
scenario_results["Buildings"]["Loss of Life (Day)"]
);
// Create HTML table using the scenario summary
let city_damage_table = floodDM.createTableFromObject({
args: { obj: scenario_results },
});
// Create enclosing div for the scenario summary
let city_damage_div = document.createElement("div");
city_damage_div.appendChild(city_damage_table);
city_damage_div.style.position = "absolute";
city_damage_div.style.float = "left";
city_damage_div.style.top = "50px";
city_damage_div.style.left = "50px";
city_damage_div.style.display = "block";
city_damage_div.style.backgroundColor = "white";
city_damage_div.style.fontSize = "x-small";
floodDM.appendCloseButton(city_damage_div);
// Add the div to the HTML page
document.body.appendChild(city_damage_div);
// return scenario summary
return scenario_results;
}
// Helper function to attach close button on div
static appendCloseButton(div) {
const cross = document.createElement("span");
cross.classList.add("close-btn");
cross.onclick = function () {
this.parentNode.remove();
return false;
};
// Set CSS styles
const styles = {
position: "absolute",
top: "10px",
right: "10px",
width: "20px",
height: "20px",
cursor: "pointer",
};
Object.assign(cross.style, styles);
// Create pseudo-elements for the cross
const beforeAfterStyles = `
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 2px;
background-color: #777;
transform: translate(-50%, -50%) rotate(45deg);
`;
const hoverStyles = `
background-color: #000000;
`;
cross.appendChild(document.createElement("style")).textContent = `
.close-btn::before,
.close-btn::after {
${beforeAfterStyles}
}
.close-btn::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
.close-btn:hover::before,
.close-btn:hover::after {
${hoverStyles}
}
`;
// Make sure the parent div has a relative position
div.style.position = "relative";
// Append the close button to the div
div.appendChild(cross);
}
// Helper function for initDamageScenario to get property loss data by depth and type
static async getPropertyDamageFunction({ params, args, data } = {}) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const propertyLossesUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/business_str_cnt_inputs.js";
const proxyUrl = `${corsProxyUrl}${propertyLossesUrl}`;
try {
const response = await fetch(proxyUrl);
let propertyLosses = await response.text();
eval(propertyLosses);
return propertyLosses;
} catch (error) {
console.error("Error fetching property losses data:", error);
return null;
}
}
// Helper function for initDamageScenario for life loss by depth
static async getLifeLossFunction({ params, args, data } = {}) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const lifeLossesUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/life_inputs.js";
const proxyUrl = `${corsProxyUrl}${lifeLossesUrl}`;
try {
const response = await fetch(proxyUrl);
let life = await response.text();
eval(life);
return life;
} catch (error) {
console.error("Error fetching life losses data:", error);
return null;
}
}
// Helper function for initDamageScenario for debris loss by depth and property type
static async getDebrisWeightFunction({ params, args, data } = {}) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const debrisUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/debris_weight.js";
const proxyUrl = `${corsProxyUrl}${debrisUrl}`;
try {
const response = await fetch(proxyUrl);
let debris = await response.text();
eval(debris);
return debris;
} catch (error) {
console.error("Error fetching debris weight data:", error);
return null;
}
}
// Helper function for initDamageScenario for bridge loss by depth
static async getBridgeDamageFunction({ params, args, data } = {}) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const debrisUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/bridge_function.js";
const proxyUrl = `${corsProxyUrl}${debrisUrl}`;
try {
const response = await fetch(proxyUrl);
let bridgeDamFun = await response.text();
eval(bridgeDamFun);
return bridgeDamFun;
} catch (error) {
console.error("Error fetching debris weight data:", error);
return null;
}
}
// Helper function for initDamageScenario for vehicle loss by depth
static async getVehicleDamageFunction({ params, args, data } = {}) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const debrisUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/vehicle_function.js";
const proxyUrl = `${corsProxyUrl}${debrisUrl}`;
try {
const response = await fetch(proxyUrl);
let vehicleDamFun = await response.text();
eval(vehicleDamFun);
return vehicleDamFun;
} catch (error) {
console.error("Error fetching debris weight data:", error);
return null;
}
}
// Helper function for initDamageScenario for utility loss by depth
static async getUtilityDamageFunction({ params, args, data } = {}) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const debrisUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/utility_function.js";
const proxyUrl = `${corsProxyUrl}${debrisUrl}`;
try {
const response = await fetch(proxyUrl);
let utilityDamFun = await response.text();
eval(utilityDamFun);
return utilityDamFun;
} catch (error) {
console.error("Error fetching debris weight data:", error);
return null;
}
}
/**
* Initialize a flood mitigation scenario for property-level analysis and intervention planning.
* Creates an interactive map displaying buildings at risk and provides tools for selecting
* individual properties to analyze mitigation options and cost-benefit calculations.
*
* @method initMitigationScenario
* @memberof floodDM
* @instance
* @async
* @param {Object} options - Configuration object for mitigation scenario
* @param {Object} [options.params] - Parameters for map rendering and visualization
* @param {string} [options.params.maptype='leaflet'] - Type of map to render: 'google' or 'leaflet'
* @param {string} [options.params.key] - Google Maps API key (required if maptype is 'google')
* @param {Object} options.args - Arguments defining the mitigation scenario
* @param {string} options.args.city - City name. Supported: 'Waterloo', 'Cedar Rapids', 'Cedar Falls'
* @param {number} options.args.depth - Flood depth in feet for analysis
* @param {Object} [options.data] - Additional data (currently unused)
* @returns {Promise<Object>} Promise resolving to city-wide damage estimates and mitigation summary
*
* @example
* // Initialize mitigation scenario for Waterloo at 30.5ft depth
* const scenario = new floodDM();
* const mitigationData = await scenario.initMitigationScenario({
* params: { maptype: 'leaflet' },
* args: {
* city: "Waterloo",
* depth: 30.5
* }
* });
*
* // Results include city-wide damage estimates
* console.log(mitigationData.low_emission); // "$ 269.2 M"
* console.log(mitigationData.high_emission); // "$ 785.6 M"
*
* @example
* // Initialize for Cedar Rapids with detailed building analysis
* const results = await scenario.initMitigationScenario({
* params: { maptype: 'leaflet' },
* args: {
* city: "Cedar Rapids",
* depth: 25.0
* }
* });
*
* // Click on building markers to select for detailed mitigation analysis
* // Use runMitigationScenario() after selection for specific building analysis
*
* @example
* // Initialize with Google Maps for Cedar Falls
* const cedarFallsData = await scenario.initMitigationScenario({
* params: {
* maptype: 'google',
* key: 'your-google-maps-api-key'
* },
* args: {
* city: "Cedar Falls",
* depth: 20.0
* }
* });
*/
async initMitigationScenario({ params, args, data } = {}) {
// Set class members of city and depth
let { city, depth } = args;
this.city = city;
this.depth = depth;
// Check params for maptype
if (params === undefined) {
this.maptype = "leaflet";
params = {};
} else if (params.maptype === undefined) this.maptype = "leaflet";
else this.maptype = params.maptype;
if (this.maptype === "google") {
if (params.key === undefined) {
this.maptype = "leaflet";
}
}
// Get building features for mitigation features
let city_features = await retrieve({
params: {
source: "mitigation_dt",
datatype: "community_flood_inundation",
},
args: { sourceType: this.city },
});
// Calculate centroid using the building features
let centroid = await floodDM.featureCollectionCentroid(city_features);
// Filter the city features retrived to get features applicable at the particular flood depth
city_features = await floodDM.getFloodInundation({
args: { depth: this.depth },
data: city_features,
});
// Render the map based on parameters
if (!this.mapRendered) {
await renderMap({
params: { maptype: this.maptype, lat: centroid[1], lon: centroid[0] },
args: { key: params.key },
});
this.mapRendered = true;
}
// If any building features exist
if (city_features.features.length > 0) {
// Define style function for the features
let styleFunction = (feature) => {
let color = "gray",
depth_style,
radius;
// Define current scenario
let current_scenario = "depth" + String(Math.floor(this.depth * 10));
// Get properties specific map type
if (this.maptype === "google") {
depth_style = feature.getProperty(current_scenario);
radius = 1;
} else if (this.maptype === "leaflet") {
depth_style = feature.properties[current_scenario];
radius = 4;
}
// Set marker color based on depth of the building
if (5 < depth_style && depth_style <= 10) color = "purple";
else if (2 < depth_style && depth_style <= 5) color = "red";
else if (0 < depth_style && depth_style <= 2) color = "yellow";
// Return style based on feature properties
return {
scale: 5,
radius: radius,
fillColor: color,
color: "black",
weight: 1,
opacity: 1,
fillOpacity: 0.8,
strokeWeight: 0.7,
};
};
// Define popup function for the features
let popUpFunction = (feature) => {
let current_scenario = "depth" + String(Math.floor(this.depth * 10));
// Destructure the properties based on maptype
let properties;
if (this.maptype === "google") {
properties = {};
feature.forEachProperty(function (value, property) {
properties[property] = value;
});
} else if (this.maptype === "leaflet") {
properties = feature.properties;
}
return `<h4><b>Damage and Mitigation</b><br></h4>
<b>Building Information</b><br>
Building ID: ${String(properties.gid)}<br>
Occupancy Type: ${String(properties.occupancy2)}<br>
Flood Depth: ${properties[current_scenario]} ft<br>`;
};
// Define click function for the features
let onClickFunction = async (event) => {
if (this.maptype === "google") {
this.selectedfeature = event.feature;
} else if (this.maptype === "leaflet") {
this.selectedfeature = event.target.feature;
}
console.log(this.selectedfeature);
};
// Render Layer for building features
await Layers({
args: {
type: "geojson",
name: "mygeoJSON",
styleFunction,
popUpFunction,
onClickFunction,
},
data: city_features,
});
}
// Get city and depth specific property damage values
let city_damage = await floodDM.getCityFloodDamage({
args: { depth: this.depth, city: this.city },
});
// Hard code emissions data for various cities as these values were hardcoded in original MiDAS system
if (city === "Cedar Rapids") {
city_damage["low_emission"] = "$ 295.3 M";
city_damage["high_emission"] = "$ 635.8 M";
} else if (city === "Cedar Falls") {
city_damage["low_emission"] = "$ 175.5 M";
city_damage["high_emission"] = "$ 332.9 M";
} else if (city === "Waterloo") {
city_damage["low_emission"] = "$ 269.2 M";
city_damage["high_emission"] = "$ 785.6 M";
}
// Create an HTML table for overlay of mitigation summary
let city_damage_table = floodDM.createTableFromObject({
args: { obj: city_damage, tableHeaderText: this.city },
});
// Create a wrapper div for table
let city_damage_div = document.createElement("div");
city_damage_div.appendChild(city_damage_table);
city_damage_div.style.position = "absolute";
city_damage_div.style.float = "left";
city_damage_div.style.top = "50px";
city_damage_div.style.left = "50px";
city_damage_div.style.display = "block";
city_damage_div.style.backgroundColor = "white";
city_damage_div.style.fontSize = "x-small";
floodDM.appendCloseButton(city_damage_div);
document.body.appendChild(city_damage_div);
// Create a HTML div
const legendHTML = `
<div id="legend" class="content"
style="display: block; background: rgba(255, 255, 255, 1); padding: 2px 5px; border-style: solid !important;">
">
<b>LEGEND</b>
<br>
<b>Depth</b>
<br>
<span style="color: purple;">■</span> > 5 - 10 ft</span>
<br>
<span style="color: red;">■</span> 2 - 5 ft</span>
<br>
<span style="color: yellow;">■</span> < 2 ft</span>
</div>
`;
const legendDiv = document.createElement("div");
legendDiv.innerHTML = legendHTML;
// Call the addCustomLegend function for Maps
await addCustomLegend({
params: {
position: "bottom right",
},
args: {
div: legendDiv,
},
});
// Return summary of city damage
return city_damage;
}
// Helper function to calculate centroid for Point feature collections.
// Pass an array of features
static featureCollectionCentroid(featureCollection) {
let numFeatures = featureCollection.features.length;
let coordSum = featureCollection.features.reduce(
(runningSum, currentFeature) => {
return [
currentFeature.geometry.coordinates[0] + runningSum[0],
currentFeature.geometry.coordinates[1] + runningSum[1],
];
},
[0, 0]
);
return [coordSum[0] / numFeatures, coordSum[1] / numFeatures];
}
/**
* Run a detailed damage and mitigation analysis for a selected property.
* Must be called after initMitigationScenario() and after clicking on a building marker to select it.
* Calculates property-specific damage estimates and evaluates mitigation measures with cost-benefit analysis.
*
* @method runMitigationScenario
* @memberof floodDM
* @instance
* @async
* @param {Object} options - Configuration object for property mitigation analysis
* @param {Object} [options.params] - Parameters (automatically set from selected building properties)
* @param {Object} options.args - Arguments defining the mitigation analysis
* @param {string} options.args.mitigationMeasure - Type of mitigation measure to evaluate (e.g., 'Elevation', 'Dry Floodproofing', 'Wet Floodproofing')
* @param {number} options.args.mitigationDepth - Depth of protection provided by mitigation measure (in feet)
* @param {string} options.args.foundationType - Foundation type of the building ('footing' or 'slab on grade')
* @param {Object} [options.data] - Additional data (currently unused)
* @returns {Promise<HTMLTableElement>} Promise resolving to HTML table element with detailed damage and mitigation summary
*
* @example
* // First initialize mitigation scenario and select a building by clicking on map
* const scenario = new floodDM();
* await scenario.initMitigationScenario({
* params: { maptype: 'leaflet' },
* args: { city: "Waterloo", depth: 30.5 }
* });
*
* // Click on a building marker on the map to select it, then run analysis
* const mitigationResults = await scenario.runMitigationScenario({
* args: {
* mitigationMeasure: 'Elevation',
* mitigationDepth: 5.0,
* foundationType: 'footing'
* }
* });
*
* @example
* // Analyze dry floodproofing for selected building
* const dryFloodproofing = await scenario.runMitigationScenario({
* args: {
* mitigationMeasure: 'Dry Floodproofing',
* mitigationDepth: 3.0,
* foundationType: 'slab on grade'
* }
* });
*
* @example
* // Analyze wet floodproofing option
* const wetFloodproofing = await scenario.runMitigationScenario({
* args: {
* mitigationMeasure: 'Wet Floodproofing',
* mitigationDepth: 2.0,
* foundationType: 'footing'
* }
* });
*
* // Results displayed as overlay table showing:
* // - Current flood damage estimates
* // - Mitigation costs
* // - Net benefit/cost of mitigation
* // - Building characteristics
*/
async runMitigationScenario({ params, args, data } = {}) {
let { mitigationMeasure, mitigationDepth, foundationType } = args;
let props;
//Check if a building feature has been selected
if (this.selectedfeature === null) {
console.log(
"Click on a property marker and then run mitigation scenario"
);
return;
}
// Destructure feature prooperties based on map
// The properties are retrieved from the property selected by click on the map
if (this.maptype === "google") {
props = {};
this.selectedfeature.forEachProperty(function (value, property) {
props[property] = value;
});
} else if (this.maptype === "leaflet") {
props = this.selectedfeature.properties;
}
// Set params based on properties of the selected feature
params = {
occupancy: props.occupancy2,
structuralValue: props.val_struct,
contentValue: props.val_cont,
buildingArea: props.sqfoot,
};
// Set args based on properties of the selected feature
args = {
floodDepth: props["depth" + (this.depth * 10).toString()],
mitigationMeasure: mitigationMeasure,
mitigationDepth: mitigationDepth,
foundationType: foundationType,
};
// Build flood damage and mitigation scenario based on provided parameters
let mitigationResult = await floodDM.buildPropertyDMScenario({
params,
args,
data,
});
// Create HTML table using the damage and mitigation metrics of the buildings
let city_damage_table = floodDM.createTableFromObject({
args: {
obj: mitigationResult,
tableHeaderText: "Damage and Mitigation Summary",
},
});
// Create wrapper for HTML table
let city_damage_div = document.createElement("div");
city_damage_div.appendChild(city_damage_table);
city_damage_div.style.position = "absolute";
city_damage_div.style.float = "left";
city_damage_div.style.top = "200px";
city_damage_div.style.left = "50px";
city_damage_div.style.display = "block";
city_damage_div.style.backgroundColor = "white";
city_damage_div.style.fontSize = "x-small";
floodDM.appendCloseButton(city_damage_div)
// Add div to page
document.body.appendChild(city_damage_div);
// Return summary
return city_damage_table;
}
/**
* Retrieves comprehensive flood damage data for a specific city and flood depth.
* Accesses pre-computed damage estimates from the MIDAS database including
* building counts, economic losses, and emission scenarios.
*
* @method getCityFloodDamage
* @memberof floodDM
* @static
* @async
* @param {Object} options - Configuration object for damage data retrieval
* @param {Object} [options.params] - Additional parameters (currently unused)
* @param {Object} options.args - Arguments for damage data query
* @param {string} options.args.city - City name for damage lookup. Supported: 'Cedar Rapids', 'Cedar Falls', 'Waterloo'
* @param {number} options.args.depth - Flood depth in feet for which to retrieve damage data
* @param {Object} [options.data] - Additional data (currently unused)
* @returns {Promise<Object|string>} Promise resolving to damage data object or error message
*
* @example
* // Get flood damage data for Cedar Rapids at 30.5ft depth
* const damageData = await floodDM.getCityFloodDamage({
* args: {
* city: "Cedar Rapids",
* depth: 30.5
* }
* });
*
* console.log(damageData.flood_level); // "30.5"
* console.log(damageData.buildings); // Number of buildings affected
* console.log(damageData.struct_loss); // Structural loss estimate
* console.log(damageData.cont_loss); // Content loss estimate
*
* @example
* // Get damage data for Waterloo at different depth
* const waterlooData = await floodDM.getCityFloodDamage({
* args: {
* city: "Waterloo",
* depth: 25.0
* }
* });
*
* @example
* // Handle invalid inputs
* const invalidData = await floodDM.getCityFloodDamage({
* args: {
* city: "InvalidCity",
* depth: 30.5
* }
* });
* // Returns: "City or depth not found"
*/
static async getCityFloodDamage({ params, args, data } = {}) {
// Validate input data
if (!args.city || !args.depth) {
return "City or depth not found";
}
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
let cityname;
if (args.city === "Cedar Falls") {
cityname = "cedar_falls";
} else if (args.city === "Cedar Rapids") {
cityname = "cedar_rapids";
} else if (args.city === "Waterloo") {
cityname = "waterloo";
}
const debrisUrl = `https://hydroinformatics.uiowa.edu/lab/midas/communities/${cityname}.json`;
const proxyUrl = `${corsProxyUrl}${debrisUrl}`;
try {
const response = await fetch(proxyUrl);
let cityDamage = await response.json();
let damage = cityDamage["city"].filter(
(item) => item["flood_level"] === args.depth.toString()
)[0];
return damage;
} catch (error) {
console.error("Error fetching debris weight data:", error);
return null;
}
}
/**
*
* Helper function to create an HTML table element from a JavaScript object.
* @method createTableFromObject
* @memberof floodDM
* @private
* @param {Object} args - An object containing the arguments for the function.
* @param {Object} args.obj - The JavaScript object to convert to an HTML table.
* @param {number} args.depth=0 - The depth level of the current object (used for indentation).
* @param {boolean} args.showHeader=true - Whether to display the object keys as table headers (bold text).
* @param {string|null} args.tableHeaderText=null - The text to display in the table header row (if provided).
* @returns {HTMLTableElement} The HTML table element created from the object.
*/
static createTableFromObject({ params, args, data } = {}) {
if (args.obj === undefined) args.obj = "";
let { obj, depth = 0, showHeader = true, tableHeaderText = null } = args;
// Create the table element
const table = document.createElement("table");
table.style.borderCollapse = "collapse"; // Collapse borders for better lines
table.style.border = "1px solid #fff"; // Add border to the table
// Add table header row if tableHeaderText is provided
if (tableHeaderText !== null && depth === 0) {
const headerRow = table.insertRow();
const headerCell = headerRow.insertCell();
headerCell.textContent = tableHeaderText;
headerCell.style.fontWeight = "bold";
headerCell.style.fontFamily = "Arial";
headerCell.colSpan = 2;
headerCell.style.borderRight = "1px solid #000";
}
// Loop through the object keys
for (const key in obj) {
// Create a new row
const row = table.insertRow();
// Insert a cell for the key
const keyCell = row.insertCell();
keyCell.textContent = key;
keyCell.style.padding = "0 10px 0 10px"; // Indent based on depth
keyCell.style.borderRight = "1px solid #fff"; // Add right border to key cell
keyCell.style.backgroundColor = "#337bcb";
keyCell.style.color = "white";
keyCell.style.fontFamily = "Arial";
// If showHeader is true, make the key bold
if (showHeader) {
keyCell.style.fontWeight = "bold";
}
// Insert a cell for the value
const valueCell = row.insertCell();
// If the value is an object, create a nested table
if (typeof obj[key] === "object" && obj[key] !== null) {
valueCell.style.borderBottom = "1px solid #fff"; // Add bottom border to value cell
valueCell.style.backgroundColor = "#fff";
valueCell.appendChild(
floodDM.createTableFromObject({
args: { obj: obj[key], depth: depth + 1, showHeader: true },
})
);
} else {
valueCell.textContent = obj[key];
}
}
return table;
}
/**
* Filters a GeoJSON FeatureCollection to return only buildings that would be flooded at a specific depth.
* Takes building features with depth-specific properties and returns only those with significant inundation.
*
* @method getFloodInundation
* @memberof floodDM
* @static
* @param {Object} options - Configuration object for flood inundation filtering
* @param {Object} [options.params] - Additional parameters (currently unused)
* @param {Object} options.args - Arguments for filtering
* @param {number} options.args.depth - Flood depth in feet for filtering buildings
* @param {Object} options.data - GeoJSON FeatureCollection with building data
* @param {string} options.data.type - Must be "FeatureCollection"
* @param {Array} options.data.features - Array of GeoJSON Feature objects with flood depth properties
* @returns {Object} Filtered GeoJSON FeatureCollection containing only buildings with flood depth >= 1 foot
*
* @example
* // Filter buildings for 20ft flood depth
* const floodedBuildings = floodDM.getFloodInundation({
* args: { depth: 20 },
* data: {
* "type": "FeatureCollection",
* "name": "waterloo",
* "crs": {
* "type": "name",
* "properties": {
* "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
* }
* },
* "features": [
* {
* "type": "Feature",
* "properties": {
* "depth200": 3.5, // Depth at 20.0ft scenario
* "gid": 1,
* "occupancy": "RES1-1SNB"
* },
* "geometry": {
* "type": "Point",
* "coordinates": [-92.39561, 42.47028]
* }
* }
* ]
* }
* });
*
* // Returns only buildings where depth200 >= 1.0
* console.log(floodedBuildings.features.length); // Number of significantly flooded buildings
*
* @example
* // Handle edge cases
* const invalidData = floodDM.getFloodInundation({
* args: { depth: "invalid" },
* data: { type: "FeatureCollection", features: [] }
* });
* // Returns: { error: "Invalid args format" }
*
* @example
* // Process different flood depths
* const depth15Buildings = floodDM.getFloodInundation({
* args: { depth: 15.5 },
* data: buildingFeatures
* });
* // Uses "depth155" property (15.5 * 10 = 155)
*/
static getFloodInundation({ params, args, data } = {}) {
// Validate input data
if (!data.features || !Array.isArray(data.features)) {
return { error: "Invalid data format" };
}
if (!args || typeof args.depth !== "number") {
return { error: "Invalid args format" };
}
const floodDepth = args.depth;
let dataClone = structuredClone(data);
dataClone.features = dataClone.features.filter((item) => {
return (
item.properties["depth" + String(Math.floor(floodDepth * 10))] >= 1
);
});
return dataClone;
}
/**
* Builds a comprehensive property damage and mitigation scenario for a specific building.
* Calculates flood damage estimates, business interruption losses, and evaluates
* mitigation options with detailed cost-benefit analysis.
*
* @method buildPropertyDMScenario
* @memberof floodDM
* @static
* @async
* @param {Object} options - Configuration object for property analysis
* @param {Object} options.params - Property characteristics and values
* @param {string} options.params.occupancy - Building occupancy type (e.g., 'RES1-1SNB', 'COM1', 'IND1', 'GOV1', 'AGR1')
* @param {number} options.params.structuralValue - Replacement value of building structure in dollars
* @param {number} options.params.contentValue - Value of building contents in dollars
* @param {number} options.params.buildingArea - Building floor area in square feet
* @param {Object} options.args - Flood scenario and mitigation parameters
* @param {number} [options.args.floodDepth] - Flood depth above building in feet
* @param {string} [options.args.mitigationMeasure] - Type of mitigation ('Elevation', 'Dry Floodproofing', 'Wet Floodproofing', etc.)
* @param {number} [options.args.mitigationDepth] - Protection depth provided by mitigation in feet
* @param {string} [options.args.foundationType] - Foundation type ('footing' or 'slab on grade')
* @param {Object} [options.data] - Additional data (currently unused)
* @returns {Promise<Object>} Promise resolving to comprehensive property analysis results
*
* @example
* // Basic property damage calculation
* const damageAnalysis = await floodDM.buildPropertyDMScenario({
* params: {
* occupancy: 'RES1-1SNB',
* structuralValue: 250000,
* contentValue: 125000,
* buildingArea: 2000
* },
* args: {
* floodDepth: 4.0
* }
* });
*
* console.log(damageAnalysis.structuralLoss); // "$45,000"
* console.log(damageAnalysis.contentLoss); // "$62,500"
*
* @example
* // Property damage with mitigation analysis
* const mitigationAnalysis = await floodDM.buildPropertyDMScenario({
* params: {
* occupancy: 'COM1',
* structuralValue: 500000,
* contentValue: 300000,
* buildingArea: 5000
* },
* args: {
* floodDepth: 6.0,
* mitigationMeasure: 'Elevation',
* mitigationDepth: 8.0,
* foundationType: 'footing'
* }
* });
*
* console.log(mitigationAnalysis.mitigationOptions.cost); // "$75,000"
* console.log(mitigationAnalysis.mitigationOptions.benefit); // "$45,000"
*
* @example
* // Compare different mitigation strategies
* const dryFloodproofing = await floodDM.buildPropertyDMScenario({
* params: {
* occupancy: 'RES1-1SNB',
* structuralValue: 200000,
* contentValue: 100000,
* buildingArea: 1800
* },
* args: {
* floodDepth: 3.0,
* mitigationMeasure: 'Dry Floodproofing',
* mitigationDepth: 5.0,
* foundationType: 'slab on grade'
* }
* });
*
* // Result includes formatted dollar values and cost-benefit ratios
*/
static async buildPropertyDMScenario({ params, args, data } = {}) {
// Destructure the required parameters and arguments
const { occupancy, structuralValue, contentValue, buildingArea } = params;
const { floodDepth, mitigationMeasure, mitigationDepth, foundationType } =
args;
// Create an object to store the result
const result = {
occupancy,
structuralValue,
contentValue,
buildingArea,
};
// Check if floodDepth is a number
if (typeof floodDepth === "number") {
// Fetch the damage data based on occupancy and flood depth
const damageData = await this.fetchCurvesData(occupancy, floodDepth);
// Calculate structural and content losses based on the damage data
const losses = this.calculateLosses(
structuralValue,
contentValue,
damageData
);
// Add flood depth, structural loss, and content loss to the result object
result.floodDepth = floodDepth;
result.structuralLoss = losses.structuralLoss;
result.contentLoss = losses.contentLoss;
let finalSqft = result.buildingArea;
// Check if mitigation measure and depth are provided
if (mitigationDepth && mitigationMeasure) {
// Get mitigation options based on foundation type, mitigation measure, mitigation depth, and building area
const mitigationOptions = await this.getMitigationOptions(
foundationType,
mitigationMeasure,
mitigationDepth,
finalSqft,
result
);
// Add mitigation options to the result object
result.mitigationOptions = mitigationOptions;
}
// Formatting loss values as US Dollar values
result.structuralLoss = floodDM.formatUSDollar(result.structuralLoss);
result.contentLoss = floodDM.formatUSDollar(result.contentLoss);
result.structuralValue = floodDM.formatUSDollar(result.structuralValue);
result.contentValue = floodDM.formatUSDollar(result.contentValue);
}
// Return the result object
return result;
}
// Helper function for buildPropertyDMScenario to retrieve curves.json
// which contains property and depth specific damage per unit area data
static async fetchCurvesData(occupancy, floodDepth) {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const curvesUrl =
"https://hydroinformatics.uiowa.edu/lab/midas/jsons/curves.json";
const proxyUrl = `${corsProxyUrl}${curvesUrl}`;
let response = await fetch(proxyUrl);
response = await response.json();
const occupancyData = response.curves.filter(
(curve) =>
curve.occupancy === occupancy && curve.depth === floodDepth.toString()
);
const closestDepth = occupancyData.reduce((prev, curr) =>
Math.abs(curr.depth - floodDepth) < Math.abs(prev.depth - floodDepth)
? curr
: prev
);
return closestDepth;
}
// Helper function for buildPropertyDMScenario to
// calculate the structural and content loss given curves data and property value
static calculateLosses(structuralValue, contentValue, damageData) {
const structuralLoss = (structuralValue * damageData.struct_dam_per) / 100;
const contentLoss = (contentValue * damageData.cont_dam_per) / 100;
return {
structuralLoss: Math.round(structuralLoss / 1000) * 1000,
contentLoss: Math.round(contentLoss / 1000) * 1000,
};
}
// Helper function for buildPropertyDMScenario to
// build mitigation scenario for selected property
static async getMitigationOptions(
foundationType,
mitigationMeasure,
mitigationDepth,
finalSqft,
result
) {
// Fetch mitigation data
const mitigationData = await this.fetchMitigationData();
let final_mitigation_cost, final_mitigation_benefit;
// Check if mitigation data is available
if (!mitigationData) {
return { error: "Failed to fetch mitigation data" };
}
const mitigationOptions = mitigationData.mitigation_options;
// Get structural loss, content loss, and final depth from the result object
let structuralLoss = result.structuralLoss;
let contentLoss = result.contentLoss;
let finalDepth = result.floodDepth;
// Iterate over each mitigation option
for (let option of mitigationOptions) {
// Check if the mitigation measure matches the option and the foundation type matches
if (
mitigationMeasure == option.measure &&
option.foundation_type === foundationType
) {
// Calculate mitigation cost and benefit for this option
final_mitigation_cost = finalSqft * option.cost;
final_mitigation_cost = Math.round(final_mitigation_cost / 1000) * 1000;
final_mitigation_benefit =
structuralLoss + contentLoss - final_mitigation_cost;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
}
// Check if the mitigation measure is "Wet Floodproofing" and the mitigation depth matches the option design
else if (
mitigationMeasure == "Wet Floodproofing" &&
mitigationDepth == option.design
) {
// Calculate mitigation cost and benefit for this option
var perimeter = Math.sqrt(finalSqft);
final_mitigation_cost = 4 * perimeter * option.cost;
final_mitigation_cost = Math.round(final_mitigation_cost / 1000) * 1000;
if (mitigationDepth < finalDepth) {
final_mitigation_benefit =
-final_mitigation_cost - structuralLoss - contentLoss;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
} else if (mitigationDepth >= finalDepth) {
final_mitigation_benefit =
structuralLoss + contentLoss - final_mitigation_cost - contentLoss;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
}
} else {
// Check if the mitigation measure and mitigation depth match the option
if (
mitigationMeasure == option.measure &&
mitigationDepth == option.design
) {
// Calculate mitigation cost and benefit based on the application type
if (option.app_type == "linear") {
var perimeter = Math.sqrt(finalSqft);
final_mitigation_cost = 4 * perimeter * option.cost;
final_mitigation_cost =
Math.round(final_mitigation_cost / 1000) * 1000;
if (mitigationDepth < finalDepth) {
final_mitigation_benefit =
-final_mitigation_cost - structuralLoss - contentLoss;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
} else if (mitigationDepth >= finalDepth) {
final_mitigation_benefit =
structuralLoss + contentLoss - final_mitigation_cost;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
}
} else if (option.app_type == "area") {
final_mitigation_cost = finalSqft * option.cost;
final_mitigation_cost =
Math.round(final_mitigation_cost / 1000) * 1000;
if (mitigationDepth < finalDepth) {
final_mitigation_benefit =
-final_mitigation_cost - structuralLoss - contentLoss;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
} else if (mitigationDepth >= finalDepth) {
final_mitigation_benefit =
structuralLoss + contentLoss - final_mitigation_cost;
final_mitigation_benefit =
Math.round(final_mitigation_benefit / 1000) * 1000;
}
}
}
}
}
// Create a return object with mitigation measure, foundation type, cost, and benefit
let returnVal = {
measure: mitigationMeasure,
foundationType: foundationType,
cost: floodDM.formatUSDollar(final_mitigation_cost),
benefit: floodDM.formatUSDollar(final_mitigation_benefit),
};
return returnVal;
}
// Helper function to convert number values to US $ formatted strings
static formatUSDollar(value) {
// Check if the input is a valid number
if (isNaN(value) || typeof value !== "number") {
return "Invalid input";
}
// Format the number with two decimal places and commas for thousands separator
const formattedValue = value.toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
return formattedValue;
}
// Helper function for getPropertyDmMt
static async fetchMitigationData() {
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const mitigationUrl =
"https://hydroinformatics.uiowa.edu/lab/midas/jsons/mitigations.json";
const proxyUrl = `${corsProxyUrl}${mitigationUrl}`;
try {
const response = await fetch(proxyUrl);
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching mitigation data:", error);
return null;
}
}
/**
* Calculates bridge damage estimates based on bridge characteristics and flood scenario.
* Evaluates structural damage and functional capacity loss for different bridge types
* under various flood return periods considering scour potential.
*
* @method getBridgeDamage
* @memberof floodDM
* @static
* @async
* @param {Object} options - Configuration object for bridge damage assessment
* @param {Object} [options.params] - Additional parameters (currently unused)
* @param {Object} options.args - Arguments for bridge damage calculation
* @param {string} options.args.bridge_type - Bridge structural type: "Single Span" or "Continuous Span"
* @param {string} options.args.scour_index - Scour vulnerability index: "Unknown", "1", "2", or "3" (1=low, 3=high risk)
* @param {string} options.args.flood_scenario - Flood return period: "25 yr", "50 yr", "100 yr", "200 yr", "500 yr"
* @param {number} options.args.replacement_value - Total replacement cost of the bridge in dollars
* @param {Object} [options.data] - Additional data (currently unused)
* @returns {Promise<Object|null>} Promise resolving to bridge damage assessment or null if no match found
*
* @example
* // Assess damage to a single span bridge in 100-year flood
* const bridgeDamage = await floodDM.getBridgeDamage({
* args: {
* bridge_type: "Single Span",
* scour_index: "2",
* flood_scenario: "100 yr",
* replacement_value: 2500000
* }
* });
*
* console.log(bridgeDamage.damagePercent); // 0.15 (15% damage)
* console.log(bridgeDamage.damageCost); // 375000 (in dollars)
*
* @example
* // Assess damage to continuous span bridge with high scour risk
* const majorBridgeDamage = await floodDM.getBridgeDamage({
* args: {
* bridge_type: "Continuous Span",
* scour_index: "3",
* flood_scenario: "500 yr",
* replacement_value: 8500000
* }
* });
*
* @example
* // Handle unknown scour conditions
* const uncertainDamage = await floodDM.getBridgeDamage({
* args: {
* bridge_type: "Single Span",
* scour_index: "Unknown",
* flood_scenario: "200 yr",
* replacement_value: 1200000
* }
* });
*/
static async getBridgeDamage({ params, args, data } = {}) {
// Destructure the required arguments from the args object
const { bridge_type, scour_index, flood_scenario, replacement_value } =
args;
// Define the URL for fetching bridge damage data and construct the proxy URL
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const bridgeDamageUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/bridge_function.js";
const proxyUrl = `${corsProxyUrl}${bridgeDamageUrl}`;
try {
// Fetch the data from the proxy URL
const response = await fetch(proxyUrl);
const data = await response.text();
// Extract the bridgeDamFun object from the fetched data
let bridgeDamFun;
eval(data);
// Find the matching entry based on bridge_type, scour_index, and flood_scenario
const matchingEntry = bridgeDamFun[bridge_type].find(
(entry) =>
entry["ScourPotential"] === scour_index &&
entry["Flood Scenario"] === flood_scenario
);
if (matchingEntry) {
// If a matching entry is found, calculate and return the bridge damage data
const damagePercent = matchingEntry["damage prcnt"];
const damageCost = replacement_value * damagePercent;
const functionalCost = replacement_value * (1 - functionalPercent);
return {
damagePercent,
damageCost: Math.round(damageCost),
damage: damage,
};
} else {
// If no matching entry is found, log a warning and return null
console.warn(
`No matching entry found for bridge_type: ${bridge_type}, scour_index: ${scour_index}, and flood_scenario: ${flood_scenario}`
);
return null;
}
} catch (error) {
// If an error occurs during the data fetch, log the error and return null
console.error("Error fetching bridge damage data:", error);
return null;
}
}
/**
* Retrieves utility damage data based on the provided arguments.
*
* @method getUtilityDamage
* @memberof floodDM
* @async
* @param {Object} params - An object containing additional parameters (currently unused).
* @param {Object} args - An object containing the required arguments.
* @param {string} args.utility - The utility system (e.g., "PDFLT", "PPPL", "WWTL", etc.).
* @param {number} args.depth - The flood depth in feet.
* @param {number} args.utilityValue - The replacement value of the utility.
* @param {Object} data - Additional data (currently unused).
* @returns {Promise<Object|null>} A Promise that resolves with an object containing the utility damage data, or null if no matching data is found or an error occurs.
*/
static async getUtilityDamage({ params, args, data } = {}) {
// Destructure the required arguments from the args object
const { utility, depth, utilityValue } = args;
// Define the URL for fetching utility damage data and construct the proxy URL
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const utilityDamageUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/utility_function.js";
const proxyUrl = `${corsProxyUrl}${utilityDamageUrl}`;
try {
// Fetch the data from the proxy URL
const response = await fetch(proxyUrl);
const data = await response.text();
// Extract the utilityDamFun object from the fetched data
let utilityDamFun;
eval(data);
// Find the matching entry based on utility and depth
const matchingEntry = utilityDamFun[utility].find(
(entry) => entry.floodDepth === depth
);
if (matchingEntry) {
// If a matching entry is found, calculate and return the utility damage data
const damagePercent = matchingEntry.damage;
const damageCost = utilityValue * (damagePercent / 100);
return {
damagePercent,
damageCost: Math.round(damageCost),
};
} else {
// If no matching entry is found, log a warning and return null
console.warn(
`No matching entry found for utility: ${utility} and depth: ${depth}`
);
return null;
}
} catch (error) {
// If an error occurs during the data fetch, log the error and return null
console.error("Error fetching utility damage data:", error);
return null;
}
}
/**
* Retrieves property loss data, including structure and content damage, business interruption losses,
* and debris amount based on the provided arguments.
*
* @method getPropertyLoss
* @memberof floodDM
* @async
* @param {Object} params - An object containing additional parameters (currently unused).
* @param {Object} args - An object containing the required arguments.
* @param {string} args.occupancy - The occupancy type (e.g., 'RES1-1SNB', 'COM1', 'IND1', etc.).
* @param {number} args.depth - The flood depth in feet.
* @param {string} args.foundationType - The foundation type ('footing' or 'slab on grade').
* @param {number} args.structureValue - The structure value of the property.
* @param {number} args.contentValue - The content value of the property.
* @param {number} args.area - The area of the property in square feet.
* @param {Object} data - Additional data (currently unused).
* @returns {Promise<Object>} A Promise that resolves with an object containing the property loss, business interruption, and debris amount data.
*/
static async getPropertyLoss({ params, args, data } = {}) {
const {
occupancy,
depth,
foundationType,
structureValue,
contentValue,
area,
} = args;
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
let propertyLosses, debris;
const propertyLossesUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/business_str_cnt_inputs.js";
const debrisUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/debris_weight.js";
const [propertyLossesData, debrisData] = await Promise.all([
fetch(`${corsProxyUrl}${propertyLossesUrl}`).then((response) =>
response.text()
),
fetch(`${corsProxyUrl}${debrisUrl}`).then((response) => response.text()),
]);
eval(propertyLossesData);
eval(debrisData);
const propertyLossEntry = propertyLosses[occupancy].find(
(entry) => entry.floodDepth === depth
);
const debrisEntry = debris[occupancy].find(
(entry) =>
entry.floodDepth === depth && entry.foundationType === foundationType
);
if (propertyLossEntry && debrisEntry) {
const structureDamPercent = propertyLossEntry.structure;
const contentDamPercent = propertyLossEntry.content;
const structureLoss = (structureValue * structureDamPercent) / 100;
const contentLoss = (contentValue * contentDamPercent) / 100;
const incomePerDay = propertyLossEntry.incomePerSqftPerDay * area;
const wagePerDay = propertyLossEntry.wagePerSqftPerDay * area;
const rentalIncome =
((1 - propertyLossEntry.percentOwnerOccupied / 100) *
area *
propertyLossEntry.rentalCostPerSqftPerDay *
propertyLossEntry.maxTime *
30) /
1000;
const relocation =
(area *
((1 - propertyLossEntry.percentOwnerOccupied / 100) *
propertyLossEntry.disruptionCostPerSqft +
(propertyLossEntry.percentOwnerOccupied / 100) *
(propertyLossEntry.disruptionCostPerSqft +
propertyLossEntry.rentalCostPerSqftPerDay *
propertyLossEntry.maxTime *
30))) /
1000;
const finishesDebris = (area * debrisEntry.finishes) / 1000;
const structureDebris = (area * debrisEntry.structure) / 1000;
const foundationDebris = (area * debrisEntry.foundation) / 1000;
const totalDebris = finishesDebris + structureDebris + foundationDebris;
return {
propertyLoss: {
structureLoss: Math.round(structureLoss),
contentLoss: Math.round(contentLoss),
structureDamPercent,
contentDamPercent,
},
businessInterruption: {
incomeLoss:
Math.round(
(1 - propertyLossEntry.recapturFactor) * incomePerDay * 30
) * propertyLossEntry.maxTime,
wageLoss:
Math.round(
(1 - propertyLossEntry.recapturFactor) * wagePerDay * 30
) * propertyLossEntry.maxTime,
rentalIncome: Math.round(rentalIncome),
relocation: Math.round(relocation),
},
debrisAmount: {
finishesDebris: Math.round(finishesDebris),
structureDebris: Math.round(structureDebris),
foundationDebris: Math.round(foundationDebris),
totalDebris: Math.round(totalDebris),
},
};
} else {
console.warn(
`No matching entry found for occupancy: ${occupancy}, depth: ${depth}, and foundationType: ${foundationType}`
);
return null;
}
}
/**
* Retrieves loss of life data based on the provided arguments.
*
* @method getLifeLoss
* @memberof floodDM
* @param {Object} params - An object containing additional parameters (currently unused).
* @param {Object} args - An object containing the required arguments.
* @param {string} args.occupancy - The occupancy type (e.g., 'RES1-1SNB', 'COM1', 'IND1', etc.).
* @param {number} args.depth - The flood depth in feet.
* @param {number} args.peopleOver65 - The number of people living in this dwelling over 65.
* @param {number} args.peopleUnder65 - he number of people living in this dwelling under 65.
* @param {Object} data - Additional data (currently unused).
* @returns {Promise<Object|null>} A Promise that resolves with an object containing the utility damage data, or null if no matching data is found or an error occurs.
*/
static async getLifeLoss({ params, args, data } = {}) {
// Destructure the required arguments from the args object
const { occupancy, depth, peopleOver65, peopleUnder65 } = args;
// Define the URL for fetching utility damage data and construct the proxy URL
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const lifeLossUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/life_inputs.js";
const proxyUrl = `${corsProxyUrl}${lifeLossUrl}`;
try {
// Fetch the data from the proxy URL
const response = await fetch(proxyUrl);
const data = await response.text();
// Extract the utilityDamFun object from the fetched data
let life;
eval(data);
// Find the matching entries for over and under 65 based on occupancy and depth
const matchingEntryOver65 = life[occupancy].find(
(entry) => entry.floodDepth === depth && entry.age === "over 65"
);
const matchingEntryUnder65 = life[occupancy].find(
(entry) => entry.floodDepth === depth && entry.age === "under 65"
);
if (matchingEntryOver65 || matchingEntryUnder65) {
return {
lifeLossOver65: parseInt(matchingEntryOver65.zone * peopleOver65),
lifeLossUnder65: parseInt(matchingEntryUnder65.zone * peopleUnder65),
};
}
} catch (error) {
// If an error occurs during the data fetch, log the error and return null
console.error("Error fetching utility damage data:", error);
return null;
}
}
/**
* Fetches vehicle damage information based on flood depth and vehicle type.
*
* @method getVehicleDamage
* @memberof floodDM
* @async
* @param {object} options - Options object containing parameters, arguments, and data.
* @param {object} options.params - Parameters object (not used in this method).
* @param {object} options.args - Arguments object containing floodDepth, vehicleType, vehicleCount, and vehicleValue.
* @param {object} options.data - Additional data (not used in this method).
* @returns {Promise<object|null>} A Promise resolving to an object containing damage and damage percentage, or null if an error occurs.
*/
static async getVehicleDamage({ params, args, data } = {}) {
const { floodDepth, vehicleType, vehicleCount, vehicleValue } = args;
// Construct the URL for fetching vehicle damage data using the proxy
const corsProxyUrl = proxies["local-proxy"]["endpoint"];
const vehicleDamageUrl =
"https://hydroinformatics.uiowa.edu/lab/fidas/data/functions/vehicle_function.js";
const proxyUrl = `${corsProxyUrl}${vehicleDamageUrl}`;
try {
// Fetch the vehicle damage data using the proxy URL
const response = await fetch(proxyUrl);
const data = await response.text();
// Parse the vehicle damage function data
let vehicleDamFun;
eval(data);
// Find the matching entry based on flood depth and vehicle type
const matchingEntry = vehicleDamFun[vehicleType].find(
(entry) => entry.floodDepth === floodDepth
);
if (matchingEntry) {
// If a matching entry is found, calculate damage and damage percentage
const damagePercent = matchingEntry.damage;
const damage = vehicleValue * (damagePercent / 100) * vehicleCount;
return {
damagePercent,
damage: Math.round(damage),
};
} else {
// If no matching entry is found, log a warning and return null
console.warn(
`No matching entry found for vehicleType: ${vehicleType} and floodDepth: ${floodDepth}`
);
return null;
}
} catch (error) {
// If an error occurs during the data fetch, log the error and return null
console.error("Error fetching vehicle damage data:", error);
return null;
}
}
}