Added advanced parameters
Added human power (constant power)
This commit is contained in:
parent
a14d37582c
commit
fa6a4f5b09
@ -80,3 +80,18 @@ input[type=number] {
|
|||||||
.battery-charge-graph text {
|
.battery-charge-graph text {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[name="parameters-accordion"], .panel-block {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.panel-heading {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#advanced-parameters:checked ~ .panel > .advanced-parameters-body,
|
||||||
|
#main-parameters:checked ~ .panel > .main-parameters-body {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
interface SimulationParameters {
|
interface SimulationParameters {
|
||||||
batteryCapacity: number,
|
batteryCapacity: number,
|
||||||
|
emptyVehicleWeight: number,
|
||||||
|
driverWeight: number,
|
||||||
additionalWeight: number,
|
additionalWeight: number,
|
||||||
|
humanPower: number,
|
||||||
|
averageSpeed: number,
|
||||||
climateZone: string,
|
climateZone: string,
|
||||||
dailyDistance: number,
|
dailyDistance: number,
|
||||||
dailyAscendingElevation: number
|
dailyAscendingElevation: number
|
||||||
@ -13,7 +17,11 @@ function runSimulation(parameters: SimulationParameters): Simulator.SimulationRe
|
|||||||
|
|
||||||
let vehicle = new Simulator.Vehicle();
|
let vehicle = new Simulator.Vehicle();
|
||||||
vehicle.batteryCapacity = parameters.batteryCapacity;
|
vehicle.batteryCapacity = parameters.batteryCapacity;
|
||||||
|
vehicle.emptyVehicleWeight = parameters.emptyVehicleWeight;
|
||||||
|
vehicle.driverWeight= parameters.driverWeight;
|
||||||
vehicle.additionalWeight = parameters.additionalWeight;
|
vehicle.additionalWeight = parameters.additionalWeight;
|
||||||
|
vehicle.humanPower = parameters.humanPower;
|
||||||
|
vehicle.averageSpeed = parameters.averageSpeed;
|
||||||
let solarIrradiance: number[] = climateData[parameters.climateZone.toLowerCase()];
|
let solarIrradiance: number[] = climateData[parameters.climateZone.toLowerCase()];
|
||||||
let planning = new Simulator.OutingPlanning(parameters.dailyDistance, parameters.dailyAscendingElevation);
|
let planning = new Simulator.OutingPlanning(parameters.dailyDistance, parameters.dailyAscendingElevation);
|
||||||
|
|
||||||
@ -56,7 +64,11 @@ function initializeSimulator(container: HTMLElement) {
|
|||||||
container.querySelector('.simulate-button').addEventListener('click', e => {
|
container.querySelector('.simulate-button').addEventListener('click', e => {
|
||||||
let parameters: SimulationParameters = {
|
let parameters: SimulationParameters = {
|
||||||
batteryCapacity: Number((<HTMLInputElement>container.querySelector('[name=battery-capacity]')).value),
|
batteryCapacity: Number((<HTMLInputElement>container.querySelector('[name=battery-capacity]')).value),
|
||||||
|
emptyVehicleWeight: Number((<HTMLInputElement>container.querySelector('[name=empty-weight]')).value),
|
||||||
|
driverWeight: Number((<HTMLInputElement>container.querySelector('[name=driver-weight]')).value),
|
||||||
additionalWeight: Number((<HTMLInputElement>container.querySelector('[name=additional-weight]')).value),
|
additionalWeight: Number((<HTMLInputElement>container.querySelector('[name=additional-weight]')).value),
|
||||||
|
humanPower: Number((<HTMLInputElement>container.querySelector('[name=human-power]')).value),
|
||||||
|
averageSpeed: Number((<HTMLInputElement>container.querySelector('[name=average-speed]')).value),
|
||||||
climateZone: (<HTMLSelectElement>container.querySelector('.zone-selector')).value,
|
climateZone: (<HTMLSelectElement>container.querySelector('.zone-selector')).value,
|
||||||
dailyDistance: Number((<HTMLInputElement>container.querySelector('[name=daily-distance]')).value),
|
dailyDistance: Number((<HTMLInputElement>container.querySelector('[name=daily-distance]')).value),
|
||||||
dailyAscendingElevation: Number((<HTMLInputElement>container.querySelector('[name=daily-elevation]')).value),
|
dailyAscendingElevation: Number((<HTMLInputElement>container.querySelector('[name=daily-elevation]')).value),
|
||||||
|
@ -1,110 +1,183 @@
|
|||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="simulation-parameters">
|
<div class="simulation-parameters">
|
||||||
<div class="wide-label">
|
|
||||||
<div class="field is-horizontal">
|
<!-- -------------- main parameters ---------------- -->
|
||||||
<div class="field-label is-normal">
|
<input type="checkbox" id="main-parameters" name="parameters-accordion" checked="checked" disabled="disabled">
|
||||||
<label class="label">Capacité de la batterie</label>
|
<div class="panel">
|
||||||
</div>
|
<label class="panel-heading" for="main-parameters">Paramètres</label>
|
||||||
<div class="field-body">
|
<div class="panel-block main-parameters-body">
|
||||||
<div class="field has-addons">
|
<div class="container wide-label">
|
||||||
<p class="control is-expanded">
|
<div class="field is-horizontal">
|
||||||
<input name="battery-capacity" class="input" type="number" min="1" value="700"/>
|
<div class="field-label is-normal">
|
||||||
</p>
|
<label class="label">Capacité de la batterie</label>
|
||||||
<p class="control">
|
</div>
|
||||||
<a class="button is-static">Wh</a>
|
<div class="field-body">
|
||||||
</p>
|
<div class="field has-addons">
|
||||||
</div>
|
<p class="control is-expanded">
|
||||||
</div>
|
<input name="battery-capacity" class="input" type="number" min="1" value="700"/>
|
||||||
</div>
|
</p>
|
||||||
|
<p class="control">
|
||||||
<div class="field is-horizontal">
|
<a class="button is-static">Wh</a>
|
||||||
<div class="field-label is-normal">
|
</p>
|
||||||
<label class="label">Poids (bagages + passagers)</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<p class="control is-expanded">
|
|
||||||
<input name="additional-weight" class="input" type="number" min="1" value="130"/>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-static">kg</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field is-horizontal">
|
|
||||||
<div class="field-label is-normal">
|
|
||||||
<label class="label">Zone climatique</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control is-expanded">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select class="zone-selector">
|
|
||||||
<option>H1a</option>
|
|
||||||
<option>H1b</option>
|
|
||||||
<option>H1c</option>
|
|
||||||
<option>H2a</option>
|
|
||||||
<option>H2b</option>
|
|
||||||
<option>H2c</option>
|
|
||||||
<option>H2d</option>
|
|
||||||
<option>H3</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="control">
|
|
||||||
<a class="button" data-activate-modal="zones-map-modal">Choix sur la carte...</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Poids passagers et chargement</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<p class="control is-expanded">
|
||||||
|
<input name="additional-weight" class="input" type="number" min="1" value="70"/>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-static">kg</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Zone climatique</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select class="zone-selector">
|
||||||
|
<option>H1a</option>
|
||||||
|
<option>H1b</option>
|
||||||
|
<option>H1c</option>
|
||||||
|
<option>H2a</option>
|
||||||
|
<option>H2b</option>
|
||||||
|
<option>H2c</option>
|
||||||
|
<option>H2d</option>
|
||||||
|
<option>H3</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="control">
|
||||||
|
<a class="button" data-activate-modal="zones-map-modal">Choix sur la carte...</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Distance quotidienne</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<p class="control is-expanded">
|
||||||
|
<input name="daily-distance" class="input" type="number" min="1" value="10">
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-static">km/jour</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Dénivelé positif quotidien</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<p class="control is-expanded">
|
||||||
|
<input name="daily-elevation" class="input" type="number" min="0" value="100">
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-static">m/jour</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="field is-horizontal">
|
|
||||||
<div class="field-label is-normal">
|
<!-- ------------------- Advanced parameters ---------------------- -->
|
||||||
<label class="label">Distance quotidienne</label>
|
<!-- set input type to "radio" to change behavior (accordion) -->
|
||||||
</div>
|
<input type="checkbox" id="advanced-parameters" name="parameters-accordion">
|
||||||
<div class="field-body">
|
<div class="panel">
|
||||||
<div class="field has-addons">
|
<label class="panel-heading" for="advanced-parameters">Paramètres avancés</label>
|
||||||
<p class="control is-expanded">
|
<div class="panel-block advanced-parameters-body">
|
||||||
<input name="daily-distance" class="input" type="number" min="1" value="10">
|
<div class="container wide-label">
|
||||||
</p>
|
<div class="field is-horizontal">
|
||||||
<p class="control">
|
<div class="field-label is-normal">
|
||||||
<a class="button is-static">km/jour</a>
|
<label class="label">Poids véhicule</label>
|
||||||
</p>
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<p class="control is-expanded">
|
||||||
|
<input name="empty-weight" class="input" type="number" min="1" value="80"/>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-static">kg</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
<div class="field is-horizontal">
|
<label class="label">Poids pilote</label>
|
||||||
<div class="field-label is-normal">
|
</div>
|
||||||
<label class="label">Dénivelé positif quotidien</label>
|
<div class="field-body">
|
||||||
</div>
|
<div class="field has-addons">
|
||||||
<div class="field-body">
|
<p class="control is-expanded">
|
||||||
<div class="field has-addons">
|
<input name="driver-weight" class="input" type="number" min="1" value="70"/>
|
||||||
<p class="control is-expanded">
|
</p>
|
||||||
<input name="daily-elevation" class="input" type="number" min="0" value="100">
|
<p class="control">
|
||||||
</p>
|
<a class="button is-static">kg</a>
|
||||||
<p class="control">
|
</p>
|
||||||
<a class="button is-static">m/jour</a>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
<div class="field is-horizontal">
|
<label class="label">Puissance pilote (pédalage)</label>
|
||||||
<div class="field-label is-normal">
|
</div>
|
||||||
</div>
|
<div class="field-body">
|
||||||
<div class="field-body">
|
<div class="field has-addons">
|
||||||
<div class="field">
|
<p class="control is-expanded">
|
||||||
<p class="control is-expanded">
|
<input name="human-power" class="input" type="number" min="0" value="100"/>
|
||||||
<a class="simulate-button button is-primary is-fullwidth">Simuler</a>
|
</p>
|
||||||
</p>
|
<p class="control">
|
||||||
|
<a class="button is-static">W</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Vitesse moyenne (hors arrêts)</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<p class="control is-expanded">
|
||||||
|
<input name="average-speed" class="input" type="number" min="1" value="20"/>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-static">km/h</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<a class="simulate-button button is-primary is-fullwidth">Simuler</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="simulation-results is-hidden">
|
<div class="simulation-results is-hidden">
|
||||||
|
@ -9,7 +9,13 @@ namespace Simulator {
|
|||||||
|
|
||||||
emptyVehicleWeight: number = 80; // kg
|
emptyVehicleWeight: number = 80; // kg
|
||||||
driverWeight: number = 60; // kg
|
driverWeight: number = 60; // kg
|
||||||
additionalWeight: number; // additional weight, not counting cyclist and empty vehicle weight, in kg
|
additionalWeight: number = 0; // additional weight, not counting cyclist and empty vehicle weight, in kg
|
||||||
|
|
||||||
|
humanPower: number = 100; // W
|
||||||
|
averageSpeed: number = 20; // average speed in km/h, when the vehicle is moving (this is important, because driver does not provide power when stopped)
|
||||||
|
|
||||||
|
nominalMotorPower: number = 250; // W
|
||||||
|
assistanceSpeedLimit: number = 25; // km/h
|
||||||
|
|
||||||
motorConsumption(distance: number, ascendingElevation: number): number {
|
motorConsumption(distance: number, ascendingElevation: number): number {
|
||||||
const g = 9.8;
|
const g = 9.8;
|
||||||
@ -18,13 +24,33 @@ namespace Simulator {
|
|||||||
potentialEnergy = potentialEnergy / 3600; // convert joules to watt-hour
|
potentialEnergy = potentialEnergy / 3600; // convert joules to watt-hour
|
||||||
|
|
||||||
// empirical measures
|
// empirical measures
|
||||||
let baseConsumption = 13; // in Wh/km
|
let baseConsumption = 13; // in Wh/km, when human power is 0
|
||||||
let maxWeight = 300; // in kg
|
let maxWeight = 300; // in kg
|
||||||
let additionalConsumptionAtMaxWeight = 5; // in Wh/km (without accounting for ascending elevation, only accelerations and additional friction)
|
let additionalConsumptionAtMaxWeight = 5; // in Wh/km (without accounting for ascending elevation, only accelerations and additional friction)
|
||||||
|
|
||||||
let weightRelatedConsumption = MathUtils.clamp(totalWeight * additionalConsumptionAtMaxWeight / maxWeight, 0, additionalConsumptionAtMaxWeight);
|
let weightRelatedConsumption = MathUtils.clamp(totalWeight * additionalConsumptionAtMaxWeight / maxWeight, 0, additionalConsumptionAtMaxWeight);
|
||||||
|
|
||||||
return distance * (baseConsumption + weightRelatedConsumption) + potentialEnergy;
|
let motorPowerLimit = this.nominalMotorPower;
|
||||||
|
let tripDuration = (distance * (baseConsumption + weightRelatedConsumption) + potentialEnergy) / (motorPowerLimit + this.humanPower);
|
||||||
|
let actualSpeed = distance / tripDuration;
|
||||||
|
console.log("Max vehicle speed, according to available power: " + (Math.round(actualSpeed*10)/10) + " km/h")
|
||||||
|
|
||||||
|
if(actualSpeed > this.assistanceSpeedLimit) {
|
||||||
|
tripDuration = distance / this.assistanceSpeedLimit
|
||||||
|
motorPowerLimit = Math.max(0, ((distance * (baseConsumption + weightRelatedConsumption) + potentialEnergy) - tripDuration * this.humanPower) / tripDuration);
|
||||||
|
tripDuration = (distance * (baseConsumption + weightRelatedConsumption) + potentialEnergy) / (motorPowerLimit + this.humanPower);
|
||||||
|
actualSpeed = distance / tripDuration;
|
||||||
|
console.log("Vehicle speed clamped by assistance speed limit, motor power limited to: " + Math.round(motorPowerLimit) + " W")
|
||||||
|
}
|
||||||
|
|
||||||
|
if(actualSpeed > this.averageSpeed) {
|
||||||
|
actualSpeed = this.averageSpeed;
|
||||||
|
tripDuration = distance / actualSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
let humanEnergy = tripDuration * this.humanPower;
|
||||||
|
|
||||||
|
return Math.max(motorPowerLimit * tripDuration, distance * (baseConsumption + weightRelatedConsumption) + potentialEnergy - humanEnergy);
|
||||||
}
|
}
|
||||||
|
|
||||||
solarPower(irradiance: number): number {
|
solarPower(irradiance: number): number {
|
||||||
|
Loading…
Reference in New Issue
Block a user