/**
* BeNomad BeMap JavaScript API - EvSmartRoutingResponse (v1.5)
*
* Strongly-typed response parser. `fromJson()` accepts both the REST **v1.0**
* shape (single `journey` + `route`) and the REST **v2.0.0** shape (`journeys[]`
* of `{summary, events[]}`), exposing both through the same getters.
*
* Wire shapes mirror `EvSmartRoutingResponse` server-side
* (`bemap_idea/.../service/{v1_0_0,v2_0_0}/model/evSmartRouting/response/EvSmartRoutingResponse.java`):
*
* {
* logTag,
* journey: { distance, duration, batteryLevel, consumed, chargingTime,
* chargingCost, departureTime, arrivalTime, vehicle,
* savedCo2Emissions },
* inputInfo,
* boundingBox,
* route: { stepPoints, polyline?, encodedPolyline?, events?, timeSlotWarns? }
* }
*
* @public
* @constructor
* @param {Object} options Parsed JSON response from the server.
*/
bemap.EvSmartRoutingResponse = function(options) {
var opts = options || {};
this.logTag = opts.logTag || null;
this.journey = opts.journey || null;
this.inputInfo = opts.inputInfo || null;
this.boundingBox = opts.boundingBox || null;
this.route = opts.route || null;
this.debugStat = opts.debugStat || null;
// REST v2.0.0 extras — populated by `_fromJsonV2`, `null` on a v1 response.
this.journeys = opts.journeys || null;
this.events = opts.events || null;
this.summary = opts.summary || null;
this.vehicleInfo = opts.vehicleInfo || null;
this.chargingCost = opts.chargingCost || null;
this.tollSumFees = opts.tollSumFees || null;
this.timeSlotWarns = opts.timeSlotWarns || null;
this.routeConsumptions = opts.routeConsumptions || null;
};
bemap.EvSmartRoutingResponse.prototype.getLogTag = function() { return this.logTag; };
bemap.EvSmartRoutingResponse.prototype.getJourney = function() { return this.journey; };
bemap.EvSmartRoutingResponse.prototype.getInputInfo = function() { return this.inputInfo; };
bemap.EvSmartRoutingResponse.prototype.getRoute = function() { return this.route; };
bemap.EvSmartRoutingResponse.prototype.getDebugStat = function() { return this.debugStat; };
/** @public @since 2.0.0 @return {Array.<Object>|null} All v2 journeys (alternatives); `null` on v1. */
bemap.EvSmartRoutingResponse.prototype.getJourneys = function() { return this.journeys; };
/** @public @since 2.0.0 @return {Array.<Object>|null} Ordered v2 route-timeline events; `null` on v1. */
bemap.EvSmartRoutingResponse.prototype.getEvents = function() { return this.events; };
/** @public @since 2.0.0 @return {Object|null} v2 primary-journey summary; `null` on v1. */
bemap.EvSmartRoutingResponse.prototype.getSummary = function() { return this.summary; };
/** @public @since 2.0.0 @return {Object|null} Vehicle info `{brand,name,variant,year}` (v2). */
bemap.EvSmartRoutingResponse.prototype.getVehicleInfo = function() { return this.vehicleInfo; };
/** @public @since 2.0.0 @return {Object|null} Charging-cost summary (v2). */
bemap.EvSmartRoutingResponse.prototype.getChargingCost = function() { return this.chargingCost; };
/** @public @since 2.0.0 @return {Object|null} Toll sum fees `{currency,feeMin,feeMax}` (v2). */
bemap.EvSmartRoutingResponse.prototype.getTollSumFees = function() { return this.tollSumFees; };
/** @public @since 2.0.0 @return {Array.<Object>|null} Time-slot warnings (v2). */
bemap.EvSmartRoutingResponse.prototype.getTimeSlotWarnings = function() { return this.timeSlotWarns; };
/** @public @since 2.0.0 @return {Array.<Object>|null} Per-point route consumptions (v2, routeDetails on). */
bemap.EvSmartRoutingResponse.prototype.getRouteConsumptions = function() { return this.routeConsumptions; };
/**
* `bemap.BoundingBox` covering the whole journey, or `null` when absent.
* `fromJson` already lifts `{topLeft, bottomRight}` to a `bemap.BoundingBox`,
* so under normal conditions this returns a `bemap.BoundingBox`. If the
* BoundingBox lift failed (e.g. malformed payload), the raw JSON object is
* returned so callers can still inspect it.
*
* @public
* @return {bemap.BoundingBox|null}
*/
bemap.EvSmartRoutingResponse.prototype.getBoundingBox = function() {
return this.boundingBox || null;
};
/**
* Charging stops in order along the journey, parsed into `EvStepPoint`.
* @public
* @return {Array.<bemap.EvStepPoint>}
*/
bemap.EvSmartRoutingResponse.prototype.getStepPoints = function() {
if (!this.route || !Array.isArray(this.route.stepPoints)) return [];
return this.route.stepPoints;
};
/**
* Route polyline as an Array of `bemap.Coordinate`, or empty when the
* server omitted the polyline (e.g. `pl=false` in the request).
* @public
* @return {Array.<bemap.Coordinate>}
*/
bemap.EvSmartRoutingResponse.prototype.getPolyline = function() {
if (!this.route || !Array.isArray(this.route.polyline)) return [];
return this.route.polyline.map(function(p) {
var lon = (typeof p.getLon === 'function') ? p.getLon() : (p.lon !== undefined ? p.lon : p.longitude);
var lat = (typeof p.getLat === 'function') ? p.getLat() : (p.lat !== undefined ? p.lat : p.latitude);
return new bemap.Coordinate(lon, lat);
});
};
/**
* Google-encoded polyline. On a **v1** response this is a single `String`; on a
* **v2** response it is an `Array.<String>` — one encoded string per ROUTE
* segment, since v2 splits the geometry across ROUTE events. `null` when absent.
* @public
* @return {String|Array.<String>|null}
*/
bemap.EvSmartRoutingResponse.prototype.getEncodedPolyline = function() {
return (this.route && this.route.encodedPolyline) ? this.route.encodedPolyline : null;
};
/**
* Distance of the whole journey, in metres.
* @public
* @return {Number} `0` when the journey is missing.
*/
bemap.EvSmartRoutingResponse.prototype.getDistance = function() {
return (this.journey && this.journey.distance) ? this.journey.distance : 0;
};
/**
* Duration of the whole journey, in seconds (including charging stops).
* @public
* @return {Number} `0` when the journey is missing.
*/
bemap.EvSmartRoutingResponse.prototype.getDuration = function() {
return (this.journey && this.journey.duration) ? this.journey.duration : 0;
};
/**
* Total time spent charging across all stops, in seconds.
* @public
* @return {Number}
*/
bemap.EvSmartRoutingResponse.prototype.getChargingTime = function() {
return (this.journey && this.journey.chargingTime) ? this.journey.chargingTime : 0;
};
/**
* Remaining battery level (percent, 0-100) at the end of the trip.
* @public
* @return {Number}
*/
bemap.EvSmartRoutingResponse.prototype.getArrivalBatteryLevel = function() {
return (this.journey && typeof this.journey.batteryLevel === 'number') ? this.journey.batteryLevel : 0;
};
/**
* Total energy consumed across the whole journey, in kWh.
* @public
* @return {Number}
*/
bemap.EvSmartRoutingResponse.prototype.getConsumed = function() {
return (this.journey && typeof this.journey.consumed === 'number') ? this.journey.consumed : 0;
};
/**
* Saved CO2 emissions (kg), when `co2Emissions` was set in the request.
* @public
* @return {Number|null}
*/
bemap.EvSmartRoutingResponse.prototype.getSavedCo2Emissions = function() {
if (!this.journey || typeof this.journey.savedCo2Emissions !== 'number') return null;
return this.journey.savedCo2Emissions;
};
/**
* Parse the raw JSON response into a `bemap.EvSmartRoutingResponse`.
*
* @public
* @param {Object} json
* @return {bemap.EvSmartRoutingResponse}
*/
bemap.EvSmartRoutingResponse.fromJson = function(json) {
if (!json) return new bemap.EvSmartRoutingResponse();
// REST v2.0.0 responses carry a `journeys[]` array; v1 carried a single `journey`.
if (Array.isArray(json.journeys)) return bemap.EvSmartRoutingResponse._fromJsonV2(json);
var route = null;
if (json.route) {
route = {
stepPoints: Array.isArray(json.route.stepPoints)
? json.route.stepPoints.map(function(s) { return new bemap.EvStepPoint(s); })
: [],
polyline: Array.isArray(json.route.polyline) ? json.route.polyline.slice() : null,
encodedPolyline: json.route.encodedPolyline || null,
events: Array.isArray(json.route.events) ? json.route.events.slice() : null,
timeSlotWarns: Array.isArray(json.route.timeSlotWarns) ? json.route.timeSlotWarns.slice() : null,
chargingStationPools: Array.isArray(json.route.chargingStationPools) ? json.route.chargingStationPools.slice() : null
};
}
var bbox = null;
if (json.boundingBox) {
var b = json.boundingBox;
if (b.topLeft && b.bottomRight) {
try {
bbox = new bemap.BoundingBox(
new bemap.Coordinate(b.topLeft.lon, b.topLeft.lat),
new bemap.Coordinate(b.bottomRight.lon, b.bottomRight.lat)
);
} catch (e) {
// Fall back to the raw payload so callers can still inspect
// it, but surface the failure on the console rather than
// swallow it silently.
if (typeof console !== 'undefined' && console.warn) {
console.warn('bemap.EvSmartRoutingResponse: bounding-box parse failed', e);
}
bbox = b;
}
} else {
bbox = b;
}
}
return new bemap.EvSmartRoutingResponse({
logTag: json.logTag || null,
journey: json.journey || null,
inputInfo: json.inputInfo || null,
boundingBox: bbox,
route: route,
debugStat: json.debugStat || null
});
};
/**
* Parse a REST **v2.0.0** response (`{ logTag, journeys: [{ summary, events[] }] }`)
* into the same public shape the v1 getters expect, plus the new v2 getters.
*
* The single-journey getters read the **primary** journey (`journeys[0]`); use
* `getJourneys()` for alternatives. The v2 event timeline is unrolled into the
* legacy `route.{stepPoints,polyline,encodedPolyline}` view: `CHARGE` events →
* `EvStepPoint`s, `ROUTE` events' `geometry` → the concatenated polyline (and
* each ROUTE leg's distance/duration/consumed is carried onto the next step
* point so `EvStepPoint.getDistance()` etc. keep working).
*
* @private
* @since 2.0.0
* @param {Object} json
* @return {bemap.EvSmartRoutingResponse}
*/
bemap.EvSmartRoutingResponse._fromJsonV2 = function(json) {
var journeys = Array.isArray(json.journeys) ? json.journeys : [];
var primary = journeys[0] || null;
var summary = (primary && primary.summary) || null;
var events = (primary && Array.isArray(primary.events)) ? primary.events : [];
// Unroll the ordered event timeline.
var stepPoints = [];
var polyline = [];
var encoded = [];
var routeConsumptions = [];
// Accumulate the ROUTE leg(s) travelled since the last step point — a via can
// split one leg into several ROUTE events — and attach the total to the next
// CHARGE, so EvStepPoint.getDistance()/getDuration()/getConsumed() keep working.
var leg = null;
function addLeg(e) {
if (!leg) leg = { distance: 0, duration: 0, consumed: 0 };
if (typeof e.distance === 'number') leg.distance += e.distance;
if (typeof e.duration === 'number') leg.duration += e.duration;
if (typeof e.consumed === 'number') leg.consumed += e.consumed;
}
for (var i = 0; i < events.length; i++) {
var evt = events[i] || {};
var type = evt.eventType;
if (type === 'ROUTE') {
if (Array.isArray(evt.geometry)) polyline = polyline.concat(evt.geometry);
if (evt.encodedGeometry) encoded.push(evt.encodedGeometry);
if (Array.isArray(evt.routeConsumptions)) routeConsumptions = routeConsumptions.concat(evt.routeConsumptions);
addLeg(evt);
} else if (type === 'CHARGE') {
stepPoints.push(bemap.EvStepPoint.fromChargeEvent(evt, leg));
leg = null; // reset — start accumulating the next segment's leg
}
// START / STOP / VIA / TOLL / EXCEPTION remain available via getEvents();
// v1 `stepPoints` were charging stops only, so only CHARGE maps here.
}
// v2 boundingBox is `{minLon,minLat,maxLon,maxLat}` (v1 was topLeft/bottomRight).
var bbox = null;
var bb = summary && summary.boundingBox;
if (bb) {
if (typeof bb.minLon === 'number') {
try {
bbox = new bemap.BoundingBox(
new bemap.Coordinate(bb.minLon, bb.maxLat), // top-left
new bemap.Coordinate(bb.maxLon, bb.minLat) // bottom-right
);
} catch (e) {
if (typeof console !== 'undefined' && console.warn) {
console.warn('bemap.EvSmartRoutingResponse: v2 bounding-box parse failed', e);
}
bbox = bb;
}
} else {
bbox = bb;
}
}
// Legacy `journey` shim so getDistance()/getDuration()/… keep working.
var journey = summary ? {
distance: summary.distance,
duration: summary.duration,
batteryLevel: summary.batteryLevel,
consumed: summary.consumed,
chargingTime: summary.chargingTime,
departureTime: summary.departureTime,
arrivalTime: summary.arrivalTime,
savedCo2Emissions: (typeof summary.savedCo2Emissions === 'number') ? summary.savedCo2Emissions : null,
vehicle: summary.vehicleInfo || null,
chargingCost: summary.chargingCost || null
} : null;
var route = {
stepPoints: stepPoints,
polyline: polyline.length ? polyline : null,
encodedPolyline: encoded.length ? encoded : null, // v2: array of per-ROUTE-segment strings
events: events
};
return new bemap.EvSmartRoutingResponse({
logTag: json.logTag || null,
journey: journey,
boundingBox: bbox,
route: route,
journeys: journeys,
events: events,
summary: summary,
vehicleInfo: (summary && summary.vehicleInfo) || null,
chargingCost: (summary && summary.chargingCost) || null,
tollSumFees: (summary && summary.tollSumFees) || null,
timeSlotWarns: (primary && Array.isArray(primary.timeSlotWarns)) ? primary.timeSlotWarns : null,
routeConsumptions: routeConsumptions.length ? routeConsumptions : null
});
};