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 = (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 += (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 = (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 = 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((container.querySelector('[name=battery-capacity]')).value), emptyVehicleWeight: Number((container.querySelector('[name=empty-weight]')).value), driverWeight: Number((container.querySelector('[name=driver-weight]')).value), additionalWeight: Number((container.querySelector('[name=additional-weight]')).value), humanPower: Number((container.querySelector('[name=human-power]')).value), speedLimit: Number((container.querySelector('[name=speed-limit]')).value), climateZone: (container.querySelector('.zone-selector')).value, dailyDistance: Number((container.querySelector('[name=daily-distance]')).value), dailyAscendingElevation: Number((container.querySelector('[name=daily-elevation]')).value), flatTerrainRatio: Number((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 = `

Il faudra recharger le vhélio sur secteur environ ${simulationResult.gridChargeCount} fois sur l'année.

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.


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)

Durée de trajet quotidienne : ${dailyDurationHours}h ${dailyDurationMinutes}min. Distance annuelle : ${Math.round(simulationResult.cumulatedDistance)} km.

` + (simulationResult.outOfBatteryDistance >= 1 ? `


/!\\ En raison d'une capacité de batterie insuffisante, il faudra faire ${Math.round(simulationResult.outOfBatteryDistance)}km/an sans assistance électrique

` : ""); //

${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.

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 += (window)['simulator.css']; let container = document.getElementById('simulator'); initializeSimulator(container); });