Last active
January 28, 2024 20:12
-
-
Save LogIN-/c7d3d7c537d46269d7e100d424dddfce to your computer and use it in GitHub Desktop.
A comprehensive JavaScript class for evaluating sailing boat performance and safety. This class, `SailingBoatMetrics`, includes methods to calculate essential metrics such as the Capsize Screening Formula (CSF), Angle of Vanishing Stability (AVS), Displacement to Length Ratio (D/L), Sail Area to Displacement Ratio (SA/D), Ballast Ratio, Length t…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Class representing the metrics of a sailing boat. | |
* | |
* This class provides functionalities to calculate various metrics relevant to a sailing boat's | |
* performance and safety. It includes methods to compute ratios and coefficients such as the | |
* Capsize Screening Formula (CSF), Displacement to Length Ratio (D/L), and others. | |
* It also allows for normalization and scoring of these metrics for a comprehensive evaluation. | |
* | |
* Measurements can be input in either metric or imperial units, with conversion capabilities | |
* included. The class provides a scoring system to evaluate the overall performance and safety | |
* of the boat based on its characteristics. | |
* | |
* Example metrics calculated: | |
* - Capsize Screening Formula (CSF) | |
* - Displacement to Length Ratio (D/L) | |
* - Sail Area to Displacement Ratio (SA/D) | |
* - Ballast Ratio | |
* - Length to Beam Ratio (LBR) | |
* - Prismatic Coefficient | |
* - Block Coefficient | |
* | |
* The class also includes a method to calculate the overall score of the sailboat based on these metrics, | |
* which can be used to gauge the boat's suitability for specific conditions or purposes. | |
* | |
* Usage: | |
* - Instantiate the class with the boat's specifications. | |
* - Call the `calculateScore()` method to evaluate the boat's performance and safety. | |
*/ | |
class SailingBoatMetrics { | |
/** | |
* Constructs an instance of the SailingBoatMetrics class. | |
* @param {Object} options - Configuration object for the sailing boat. | |
* @param {number} options.displacement - The displacement of the boat. | |
* @param {number} options.beam - The beam (width) of the boat. | |
* @param {number} options.LWL - The Length at Water Line of the boat. | |
* @param {number} options.sailArea - The sail area of the boat. | |
* @param {number} options.ballastWeight - The ballast weight of the boat. | |
* @param {number} options.LOA - The Length Overall of the boat. | |
* @param {number} options.midshipArea - The midship area of the boat. | |
* @param {number} options.draft - The draft of the boat. | |
* @param {number} options.AVS - The Angle of Vanishing Stability (optional). | |
* @param {boolean} options.isMetric - Flag to indicate if the measurements are in metric units. | |
*/ | |
constructor(options, boatType = 'liveaboard') { | |
this.displacement = options.displacement; | |
this.beam = options.beam; | |
this.LWL = options.LWL; | |
this.sailArea = options.sailArea; | |
this.ballastWeight = options.ballastWeight; | |
this.LOA = options.LOA; | |
this.midshipArea = options.midshipArea; | |
this.draft = options.draft; | |
this.AVS = options.AVS || 0; | |
this.isMetric = options.isMetric || false; | |
this.boatType = boatType; | |
this.validateInputs(); | |
this.convertToImperialIfNeeded(); | |
} | |
/** | |
* Validates the input properties. Throws an error if any property is invalid. | |
*/ | |
validateInputs() { | |
const properties = ['displacement', 'beam', 'LWL', 'sailArea', 'LOA', 'draft']; | |
properties.forEach(prop => { | |
if (typeof this[prop] !== 'number' || this[prop] <= 0) { | |
throw new Error(`Invalid value for ${prop}`); | |
} | |
}); | |
if (this.ballastWeight !== undefined && (typeof this.ballastWeight !== 'number' || this.ballastWeight < 0)) { | |
throw new Error("Invalid value for ballastWeight"); | |
} | |
if (this.midshipArea !== undefined && (typeof this.midshipArea !== 'number' || this.midshipArea <= 0)) { | |
throw new Error("Invalid value for midshipArea"); | |
} | |
} | |
/** | |
* Validates the input properties. Throws an error if any property is invalid. | |
*/ | |
convertToImperialIfNeeded() { | |
if (this.isMetric) { | |
this.displacement *= 2.20462; | |
this.beam *= 3.28084; | |
this.LWL *= 3.28084; | |
this.sailArea *= 10.7639; | |
this.ballastWeight *= 2.20462; | |
this.LOA *= 3.28084; | |
this.midshipArea *= 10.7639; | |
this.draft *= 3.28084; | |
} | |
} | |
/** | |
* Calculates the Capsize Screening Formula (CSF). | |
* @return {number} The CSF score. | |
*/ | |
capsizeScreeningFormula() { | |
return this.beam / Math.cbrt(this.displacement); | |
} | |
/** | |
* Sets the Angle of Vanishing Stability. | |
* @param {number} angle - The angle to set for AVS. | |
*/ | |
setAngleOfVanishingStability(angle) { | |
if (typeof angle !== 'number' || angle < 0) { | |
throw new Error("Invalid value for angle"); | |
} | |
this.AVS = angle; | |
} | |
/** | |
* Calculates the Displacement to Length Ratio (D/L). | |
* @return {number} The D/L ratio. | |
*/ | |
displacementToLengthRatio() { | |
return (this.displacement * 2240) / (0.01 * Math.pow(this.LWL, 3)); | |
} | |
/** | |
* Calculates the Sail Area to Displacement Ratio (SA/D). | |
* @return {number} The SA/D ratio. | |
*/ | |
sailAreaToDisplacementRatio() { | |
return this.sailArea / Math.cbrt(Math.pow(this.displacement, 1 / 35.3147)); | |
} | |
/** | |
* Calculates the Ballast Ratio. | |
* @return {number|undefined} The Ballast Ratio, or undefined if ballastWeight is not set. | |
*/ | |
ballastRatio() { | |
if (this.ballastWeight === undefined) { | |
return undefined; | |
} | |
return this.ballastWeight / this.displacement; | |
} | |
/** | |
* Calculates the Length to Beam Ratio (LBR). | |
* @return {number} The LBR score. | |
*/ | |
lengthToBeamRatio() { | |
return this.LOA / this.beam; | |
} | |
/** | |
* Calculates the Prismatic Coefficient. | |
* @return {number|undefined} The Prismatic Coefficient, or undefined if midshipArea is not set. | |
*/ | |
prismaticCoefficient() { | |
if (this.midshipArea === undefined) { | |
return undefined; | |
} | |
return this.displacement / (this.LWL * this.midshipArea * this.LWL); | |
} | |
/** | |
* Calculates the Block Coefficient. | |
* @return {number} The Block Coefficient score. | |
*/ | |
blockCoefficient() { | |
return this.displacement / (this.LWL * this.beam * this.draft); | |
} | |
/** | |
* Calculates an estimated Living Space Index (LSI) based on boat dimensions. | |
* @return {number} Estimated LSI score. | |
*/ | |
calculateLivingSpaceIndex() { | |
// Simplistic calculation: Use a combination of LOA, Beam, and Draft | |
return this.LOA * this.beam * this.draft; | |
} | |
/** | |
* Normalizes the Capsize Screening Formula score. | |
* @param {number} value - The CSF value to normalize. | |
* @return {number} The normalized CSF score. | |
*/ | |
normalizeCSF(value) { | |
// Adjusted range for Capsize Screening Formula | |
return this.isValidNumber(value) ? this.normalizeValue(value, 1.7, 2.0) : 5.5; | |
} | |
normalizeAVS(value) { | |
// Adjusted range for Angle of Vanishing Stability | |
return this.isValidNumber(value) ? this.normalizeValue(value, 130, 150) : 5.5; | |
} | |
normalizeDL(value) { | |
// Adjusted range for Displacement to Length Ratio | |
return this.isValidNumber(value) ? this.normalizeValue(value, 150, 300) : 5.5; | |
} | |
normalizeSAD(value) { | |
// Adjusted range for Sail Area to Displacement Ratio | |
return this.isValidNumber(value) ? this.normalizeValue(value, 16, 24) : 5.5; | |
} | |
normalizeBR(value) { | |
// Adjusted range for Ballast Ratio | |
return this.isValidNumber(value) ? this.normalizeValue(value * 100, 35, 55) : 5.5; | |
} | |
normalizeLBR(value) { | |
// Adjusted range for Length to Beam Ratio | |
return this.isValidNumber(value) ? this.normalizeValue(value, 2.7, 3.5) : 5.5; | |
} | |
normalizeCP(value) { | |
// Adjusted range for Prismatic Coefficient | |
return this.isValidNumber(value) ? this.normalizeValue(value, 0.53, 0.63) : 5.5; | |
} | |
normalizeCB(value) { | |
// Adjusted range for Block Coefficient | |
return this.isValidNumber(value) ? this.normalizeValue(value, 0.33, 0.43) : 5.5; | |
} | |
normalizeLSI(value, lsiMin, lsiMax) { | |
// Normalize the Living Space Index (LSI) | |
return this.isValidNumber(value) ? this.normalizeValue(value, lsiMin, lsiMax) : 5.5; | |
} | |
/** | |
* Calculates the overall score of the sailboat based on various metrics. | |
* @return {number} The calculated score. | |
*/ | |
calculateScore() { | |
// Define LSI min and max for a couple | |
const lsiMin = 100; // Represents modest living space | |
const lsiMax = 300; // Represents ideal living space | |
// Define default weights | |
let weights = this.getDefaultWeights(); | |
// Adjust weights based on boat type | |
switch (this.boatType) { | |
case 'racer': | |
weights = this.getRacerWeights(); | |
break; | |
case 'cruiser': | |
weights = this.getCruiserWeights(); | |
break; | |
case 'liveaboard': | |
weights = this.getLiveaboardCruiserWeights(); | |
weights.lsi = 0.2; // Assign significant weight to living space for liveaboards | |
break; | |
// Add more cases for other boat types | |
default: | |
// Keep default weights | |
break; | |
} | |
// Include LSI in the score calculation for liveaboards | |
const lsiScore = this.boatType === 'liveaboard' ? this.normalizeLSI(this.calculateLivingSpaceIndex(), lsiMin, lsiMax) : 0; | |
// Calculate the total score based on the adjusted weights | |
const totalScore = (this.normalizeCSF(this.capsizeScreeningFormula()) * weights.csf + | |
this.normalizeAVS(this.AVS) * weights.avs + | |
this.normalizeDL(this.displacementToLengthRatio()) * weights.dl + | |
this.normalizeSAD(this.sailAreaToDisplacementRatio()) * weights.sad + | |
this.normalizeBR(this.ballastRatio()) * weights.br + | |
this.normalizeLBR(this.lengthToBeamRatio()) * weights.lbr + | |
this.normalizeCP(this.prismaticCoefficient()) * weights.cp + | |
this.normalizeCB(this.blockCoefficient()) * weights.cb + | |
lsiScore * weights.lsi) / | |
(Object.values(weights).reduce((a, b) => a + b) + (this.boatType === 'liveaboard' ? weights.lsi : 0)); // Normalize the total by the sum of weights | |
return totalScore; | |
} | |
/** | |
* Normalizes a given value based on a specified range. | |
* @param {number} value - The value to normalize. | |
* @param {number} min - The minimum of the range. | |
* @param {number} max - The maximum of the range. | |
* @return {number} The normalized value. | |
*/ | |
normalizeValue(value, min, max) { | |
if (value <= min) return 10; | |
if (value >= max) return 1; | |
return 10 - ((value - min) / (max - min) * 9); | |
} | |
/** | |
* Checks if a value is a valid number. | |
* @param {number} value - The value to check. | |
* @return {boolean} True if the value is a valid number, false otherwise. | |
*/ | |
isValidNumber(value) { | |
return typeof value === 'number' && !isNaN(value); | |
} | |
// Helper methods to get weights for different boat types | |
getDefaultWeights() { | |
return { | |
csf: 0.15, | |
avs: 0.15, | |
dl: 0.15, | |
sad: 0.15, | |
br: 0.1, | |
lbr: 0.1, | |
cp: 0.1, | |
cb: 0.1 | |
}; | |
} | |
getRacerWeights() { | |
return { | |
csf: 0.1, | |
avs: 0.1, | |
dl: 0.1, | |
sad: 0.25, // Higher importance for Sail Area to Displacement | |
br: 0.05, | |
lbr: 0.15, // Higher importance for Length to Beam Ratio | |
cp: 0.15, | |
cb: 0.1 | |
}; | |
} | |
getCruiserWeights() { | |
return { | |
csf: 0.2, | |
avs: 0.2, | |
dl: 0.15, | |
sad: 0.1, | |
br: 0.1, | |
lbr: 0.05, | |
cp: 0.1, | |
cb: 0.1 | |
}; | |
} | |
getLiveaboardCruiserWeights() { | |
return { | |
csf: 0.2, // Stability is crucial for liveaboards | |
avs: 0.2, // High priority for safety in various sea conditions | |
dl: 0.15, // Displacement to Length is important for comfort in waves | |
sad: 0.1, // Sail Area to Displacement is less crucial for liveaboards | |
br: 0.1, // Adequate ballast for stability | |
lbr: 0.05, // Length to Beam Ratio is less critical | |
cp: 0.1, // Prismatic Coefficient for efficient hull shape | |
cb: 0.1 // Block Coefficient for seaworthiness | |
} | |
} | |
/** | |
* Example usage of the SailingBoatMetrics class. | |
*/ | |
static exampleUsage() { | |
let options = { | |
displacement: 40786, // in pounds | |
beam: 14.76, // in feet | |
LWL: 43.47, // in feet | |
sailArea: 1328.27, // in square feet | |
ballastWeight: 17086, // in pounds | |
LOA: 49.18, // in feet | |
midshipArea: undefined, // This value is not provided, so we set it to undefined | |
draft: 7.71, // in feet | |
AVS: undefined, // Angle of Vanishing Stability is not provided | |
isMetric: false // Measurements are in imperial units | |
}; | |
let sailingBoat = new SailingBoatMetrics(options); | |
let score = sailingBoat.calculateScore(); | |
console.log("Hallberg-Rassy 48 Sailboat Score:", score); | |
} | |
} | |
SailingBoatMetrics.exampleUsage(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment