interface SimulationParameters {
batteryCapacity : number ;
emptyVehicleWeight : number ;
driverWeight : number ;
additionalWeight : number ;
humanPower : number ;
speedLimit : number ;
climateZone : string ;
dailyDistance : number ;
dailyAscendingElevation : number ;
flatTerrainRatio : 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 . emptyVehicleWeight = parameters . emptyVehicleWeight ;
vehicle . driverWeight = parameters . driverWeight ;
vehicle . additionalWeight = parameters . additionalWeight ;
vehicle . humanPower = parameters . humanPower ;
vehicle . speedLimit = parameters . speedLimit ;
let solarIrradiance : number [ ] = climateData [ parameters . climateZone . toLowerCase ( ) ] ;
let planning = new Simulator . OutingPlanning ( parameters . dailyDistance , parameters . dailyAscendingElevation , parameters . flatTerrainRatio ) ;
let simulationResult = Simulator . simulate ( vehicle , solarIrradiance , planning ) ;
//console.log(solarIrradiance);
//console.log(simulationResult);
return simulationResult ;
}
function initializeSimulator ( container : HTMLElement ) {
// 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 ) ,
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 ) ,
humanPower : Number ( ( < HTMLInputElement > container . querySelector ( '[name=human-power]' ) ) . value ) ,
speedLimit : Number ( ( < HTMLInputElement > container . querySelector ( '[name=speed-limit]' ) ) . 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 ) ,
flatTerrainRatio : Number ( ( < HTMLInputElement > container . querySelector ( '[name=flat-ratio]' ) ) . value ) / 100.0 ,
} ;
let simulationResult = runSimulation ( parameters ) ;
console . log ( simulationResult ) ;
let resultsContainer = container . querySelector ( '.simulation-results' ) ;
let averageKwhCost = 0.1558 ; // in €/kWh
let totalConsumedGridPower = simulationResult . cumulatedGridRechargeEnergy / simulationResult . vehicle . batteryEfficiency / simulationResult . vehicle . gridTransformerEfficiency ;
let solarRechargeRatio = Math . round ( 100 * ( simulationResult . cumulatedSolarRechargeEnergy / ( simulationResult . cumulatedSolarRechargeEnergy + simulationResult . cumulatedGridRechargeEnergy ) ) ) ;
let dailyDuration = parameters . dailyDistance / simulationResult . averageSpeed ;
let dailyDurationHours = Math . floor ( dailyDuration ) ;
let dailyDurationMinutes = Math . round ( ( dailyDuration - dailyDurationHours ) * 60 ) ;
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 ( totalConsumedGridPower / 1000 * averageKwhCost * 100 ) / 100 } € sur l ' année . Le vhélio sera rechargé à $ { solarRechargeRatio } % par le soleil , $ { 100 - solarRechargeRatio } % sur secteur . < / p >
< p > < br / > < / p >
< p > Vitesse moyenne : $ { Math . round ( simulationResult . averageSpeed * 10.0 ) / 10.0 } km / h ( $ { Math . round ( simulationResult . flatTerrainSpeed * 10.0 ) / 10.0 } km / h sur plat , $ { Math . round ( simulationResult . uphillSpeed * 10.0 ) / 10.0 } km / h en côte à $ { Math . round ( ( parameters . dailyAscendingElevation / 1000.0 ) / ( parameters . dailyDistance * ( 1.0 - parameters . flatTerrainRatio ) * 0.5 ) * 100.0 ) } % , $ { Math . round ( simulationResult . downhillSpeed * 10.0 ) / 10.0 } km / h en descente ) < / p >
< p > Durée de trajet quotidienne : $ { dailyDurationHours } h $ { dailyDurationMinutes } min . Distance annuelle : $ { Math . round ( simulationResult . cumulatedDistance ) } km . < / p >
`
+ ( simulationResult . outOfBatteryDistance >= 1 ? ` <p><br/></p><p class="simulation-warning">/! \\ En raison d'une capacité de batterie insuffisante, il faudra faire ${ Math . round ( simulationResult . outOfBatteryDistance ) } km/an sans assistance électrique</p> ` : "" ) ;
//<p>${Math.round(100*(simulationResult.cumulatedSolarRechargeEnergy/simulationResult.vehicle.batteryEfficiency) / simulationResult.totalProducedSolarEnergy)}% de l'énergie produite par le panneau photovoltaïque sera utilisée pour recharger le vhélio.</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 , idx ) = > x == 0 || idx == simulationResult . batteryLevel . length - 2 ? 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 ) ;
} ) ;
}
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
// Load CSS
document . getElementsByTagName ( 'head' ) [ 0 ] . innerHTML += ( < any > window ) [ 'simulator.css' ] ;
let container = document . getElementById ( 'simulator' ) ;
initializeSimulator ( container ) ;
} ) ;