Source: bemap-services-v2/ev-routing/bemap-ev-smart-routing.js

/**
 * BeNomad BeMap JavaScript API - EvSmartRouting service (v1.5)
 *
 * Promise-based wrapper around `POST service/2.0/evsmartrouting` (REST v2.0.0).
 *
 * **What it does.** Given a start, a stop, and a vehicle key, the service
 * plans an EV journey that respects the battery's state of charge — adding
 * charging stops where needed using the configured CSP(s). The response
 * carries the journey summary (distance, duration, charging time, CO2)
 * plus an ordered list of `EvStepPoint`s with energy budgets and the
 * route polyline.
 *
 * Auth: requires `ROLE_EVSMARTROUTING`. The CSP(s) must be in the user's
 * allowed list (see `bemap.helpers.listChargingStationProviders`).
 *
 * Typical usage:
 *
 *     var router = new bemap.EvSmartRouting(ctx);
 *     var req = new bemap.EvSmartRoutingRequest({
 *         start: new bemap.Coordinate(2.35, 48.85),
 *         stop:  new bemap.Coordinate(4.83, 45.75),
 *         vehicle: 'renault-megane-e-tech-ev60',
 *         chargingStationProviders: ['ecoMovement'],
 *         initBatteryLevel: 80,
 *         minBatteryLevel: 10,
 *         minArrivalBatteryLevel: 20
 *     });
 *     router.calculate(req).then(function(response) {
 *         drawPolyline(response.getPolyline());
 *         response.getStepPoints().forEach(addChargingMarker);
 *         summary(response.getDistance(), response.getDuration(), response.getChargingTime());
 *     });
 *
 * @since 1.5.0
 * @public
 * @constructor
 * @extends {bemap.ServiceV2}
 * @param {bemap.Context} context BeMap-JS-API Context. Mandatory.
 * @param {Object} [options]
 */
bemap.EvSmartRouting = function(context, options) {
    bemap.ServiceV2.call(this, context, options);
};
bemap.inherits(bemap.EvSmartRouting, bemap.ServiceV2);

/**
 * Plan an EV journey with optional charging stops.
 *
 * @public
 * @param {bemap.EvSmartRoutingRequest} request
 * @param {Object} [options]
 * @param {AbortSignal} [options.signal] AbortSignal for cancellation.
 * @return {Promise<bemap.EvSmartRoutingResponse>}
 */
bemap.EvSmartRouting.prototype.calculate = function(request, options) {
    if (!(request instanceof bemap.EvSmartRoutingRequest)) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvSmartRouting.calculate requires a bemap.EvSmartRoutingRequest'
        }));
    }
    var opts = options || {};
    var body = request.toJsonV2();
    if (!body.geoserver) body.geoserver = this.ctx.getGeoserver();
    // Pre-fill the CSP from the Context when the request did not specify one.
    if ((!body.csps || !body.csps.length) && this.ctx.chargingStationProvider) {
        body.csps = [this.ctx.chargingStationProvider];
    }
    // v2 `start` / `stop` are structured places with a mandatory `lon`/`lat`
    // (@JsonProperty(required = true) in v2_0_0 EvSmartRoutingRequest / PlaceFront).
    // Trap the missing-coordinate case client-side so consumers get a typed error
    // rather than an opaque 400. `== null` catches null/undefined while letting a
    // legitimate `0` (Prime-Meridian / Equator) through.
    var s = body.start, e = body.stop;
    if (!s || s.lon == null || s.lat == null || !e || e.lon == null || e.lat == null) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvSmartRouting.calculate: start and stop coordinates are required'
        }));
    }
    return this._post('service/2.0/evsmartrouting', body, { signal: opts.signal })
        .then(function(json) {
            var resp = bemap.EvSmartRoutingResponse.fromJson(json);
            // A journey was returned if the v2 `journeys[]` has an entry (or, for a
            // v1-shaped payload, `journey` is present). Gating on the summary-derived
            // shim alone would falsely throw when a journey omits its summary.
            var journeys = resp.getJourneys();
            var hasJourney = (journeys && journeys.length) || !!resp.getJourney();
            if (!hasJourney) {
                throw new bemap.Error({
                    code: bemap.Error.EV_NO_JOURNEY,
                    message: 'No EV journey could be planned between the supplied points'
                });
            }
            return resp;
        });
};

/**
 * Fetch the list of EV brands the current user has access to. Wraps
 * `GET service/evsmartrouting/vehiclebrands/list/1.0`.
 *
 * @public
 * @param {Object} [options]
 * @param {AbortSignal} [options.signal]
 * @return {Promise<Array.<Object>>} Raw `brands[]` array (each entry has `id` and `name`).
 */
bemap.EvSmartRouting.prototype.getBrands = function(options) {
    var opts = options || {};
    return this._get('service/evsmartrouting/vehiclebrands/list/1.0', { signal: opts.signal })
        .then(function(json) {
            return (json && Array.isArray(json.brands)) ? json.brands : [];
        });
};

/**
 * Fetch the list of EV vehicle models. Wraps
 * `GET service/evsmartrouting/vehiclemodels/list/1.0`.
 *
 * @public
 * @param {Object} [options]
 * @param {String} [options.vehicle]   Filter by vehicle key.
 * @param {String} [options.brandId]   Filter by brand id.
 * @param {AbortSignal} [options.signal]
 * @return {Promise<Array.<Object>>} Raw `vehicles[]` array.
 */
bemap.EvSmartRouting.prototype.getVehicles = function(options) {
    var opts = options || {};
    var qs = [];
    if (opts.vehicle) qs.push('vehicle=' + encodeURIComponent(opts.vehicle));
    if (opts.brandId) qs.push('brandId=' + encodeURIComponent(opts.brandId));
    var path = 'service/evsmartrouting/vehiclemodels/list/1.0' + (qs.length ? '?' + qs.join('&') : '');
    return this._get(path, { signal: opts.signal })
        .then(function(json) {
            return (json && Array.isArray(json.vehicles)) ? json.vehicles : [];
        });
};