Vehicle energy consumption and production simulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

115 lines
5.9 KiB

interface SimulationParameters {
batteryCapacity: number,
additionalWeight: number,
climateZone: string,
dailyDistance: number,
dailyAscendingElevation: number
}
function runSimulation(parameters: SimulationParameters): Simulator.SimulationResult {
let climateData = (<any>window)['climate-zones-data.csv'];
let vehicle = new Simulator.Vehicle();
vehicle.batteryCapacity = parameters.batteryCapacity;
vehicle.additionalWeight = parameters.additionalWeight;
let solarIrradiance: number[] = climateData[parameters.climateZone.toLowerCase()];
let planning = new Simulator.OutingPlanning(parameters.dailyDistance, parameters.dailyAscendingElevation);
let simulationResult = Simulator.simulate(vehicle, solarIrradiance, planning);
//console.log(solarIrradiance);
//console.log(simulationResult);
//let averageKwhCost = 0.192; // in €/kWh TODO: to verify, this price seems too high
//console.log('Grid recharge cost: ' + (Math.round(simulationResult.gridChargeCount*(vehicle.batteryCapacity/1000)*averageKwhCost*100)/100) + '€');
//console.log('Solar energy ratio: ' + Math.round(100*(simulationResult.cumulatedMotorConsumption-(simulationResult.gridChargeCount+1)*vehicle.batteryCapacity)/simulationResult.cumulatedMotorConsumption) + '%');
return simulationResult;
}
document.addEventListener('DOMContentLoaded', function() {
let container = document.getElementById('simulator');
// Insert HTML code in the container
container.innerHTML += (<any>window)['simulator.html'];
// In order to be able to style SVG elements with CSS, and register events with javascript, we must use inline SVG (we can't use an img tag)
// For this purpose, the SVG file contents are embedded in a javascript file
container.querySelector('#zones-map').innerHTML = (<any>window)['climate-zones-map.svg'];
container.querySelectorAll("[data-activate-modal]").forEach(elt => {
elt.addEventListener('click', e => {
container.querySelector('#'+elt.getAttribute('data-activate-modal')).classList.toggle('is-active', true);
});
});
container.querySelectorAll('.modal-close, .modal-card-head .delete').forEach(elt => {
elt.addEventListener('click', e => {
HtmlUtils.closest(elt, e => e.classList.contains('modal')).classList.toggle('is-active', false);
});
});
let zoneSelector = <HTMLSelectElement>container.querySelector('#zone-selector');
container.querySelectorAll('.climate-zone').forEach(elt => {
elt.addEventListener('click', e => {
let zoneName = elt.getAttribute('id');
zoneSelector.value = zoneName;
HtmlUtils.closest(elt, e => e.classList.contains('modal')).classList.toggle('is-active', false);
});
});
container.querySelector('#simulate-button').addEventListener('click', e => {
let parameters: SimulationParameters = {
batteryCapacity: Number((<HTMLInputElement>container.querySelector('[name=battery-capacity]')).value),
additionalWeight: Number((<HTMLInputElement>container.querySelector('[name=additional-weight]')).value),
climateZone: (<HTMLSelectElement>container.querySelector('#zone-selector')).value,
dailyDistance: Number((<HTMLInputElement>container.querySelector('[name=daily-distance]')).value),
dailyAscendingElevation: Number((<HTMLInputElement>container.querySelector('[name=daily-elevation]')).value),
};
let simulationResult = runSimulation(parameters);
let resultsContainer = container.querySelector('.simulation-results');
let averageKwhCost = 0.192; // in €/kWh TODO: to verify, this price seems too high
resultsContainer.querySelector('.result-info').innerHTML = `
<p>Il faudra recharger le vhélio sur secteur environ ${simulationResult.gridChargeCount} fois sur l'année</p>
<p>Cela coûtera ${Math.round(simulationResult.gridChargeCount*(parameters.batteryCapacity/1000)*averageKwhCost*100)/100}€ sur l'année</p>
<p>La couverture solaire du vhélio est de ${Math.round(100*(simulationResult.cumulatedMotorConsumption-(simulationResult.gridChargeCount+1)*parameters.batteryCapacity)/simulationResult.cumulatedMotorConsumption)}%</p>
`;
let batteryChargeGraph = new SvgDrawing.SvgElement(resultsContainer.querySelector('.battery-charge-graph svg'));
batteryChargeGraph.clear();
let marginTop = 10;
let marginBottom = 20;
let marginLeft = 35;
let marginRight = 5;
batteryChargeGraph.line({ x: marginLeft, y: marginBottom }, { x: batteryChargeGraph.width - marginRight, y: marginBottom });
batteryChargeGraph.line({ x: marginLeft, y: marginBottom }, { x:marginLeft, y: batteryChargeGraph.height - marginTop });
batteryChargeGraph.text({ x: marginLeft-3, y: marginBottom }, '0%', 'end', 0.6);
batteryChargeGraph.text({ x: marginLeft-3, y: batteryChargeGraph.height - marginTop }, '100%', 'end', 0.6);
batteryChargeGraph.viewport.setData({ x: 0, y: 0, width: 365*24, height: parameters.batteryCapacity });
batteryChargeGraph.viewport.setView({ x: marginLeft, y: batteryChargeGraph.height - marginBottom, width: batteryChargeGraph.width - (marginLeft+marginRight), height: -batteryChargeGraph.height+(marginTop+marginBottom) });
batteryChargeGraph.graph(simulationResult.batteryLevel, simulationResult.batteryLevel.map(x => x == 0 ? 1 : 0), [{className: ''}, {className: 'grid-recharge'}]);
let months = ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jui', 'Jui', 'Aou', 'Sep', 'Oct', 'Nov', 'Dec'];
let monthWidth = 365*24/12
for(let month = 0; month < 12; ++month) {
batteryChargeGraph.text({ x: (month+0.5)*monthWidth, y: 0 }, months[month], 'middle', -0.1);
}
batteryChargeGraph.viewport.setData({ x: 0, y: 0, width: 365*24, height: 1 });
batteryChargeGraph.viewport.setView({ x: marginLeft, y: batteryChargeGraph.height - marginBottom, width: batteryChargeGraph.width - (marginLeft+marginRight), height: -15 });
for(let month = 0; month < 13; ++month) {
batteryChargeGraph.line({ x: month*monthWidth, y: 0 }, { x: month*monthWidth, y: -1 });
}
resultsContainer.classList.toggle('is-hidden', false);
});
});