Source: bemap-services-v2/ev/bemap-ev-reachable-area.js

/**
 * BeNomad BeMap JavaScript API - EvReachableArea service (v1.5)
 *
 * Promise-based wrapper around `POST service/evreachablearea/1.0`. Given
 * a start coordinate, an EV vehicle, and an initial battery level, returns
 * the polygon of every point reachable from the start.
 *
 * Auth: `ROLE_EVREACHABLEAREA`.
 *
 * Typical usage:
 *
 *     var era = new bemap.EvReachableArea(ctx);
 *     var req = new bemap.EvReachableAreaRequest({
 *         start: new bemap.Coordinate(2.35, 48.85),
 *         vehicle: 'a204c816-...',
 *         temperature: 18,
 *         initBatteryLevel: 80
 *     });
 *     era.compute(req).then(function(response) {
 *         var polygon = new bemap.Polygon(response.getPolygon(), {
 *             style: new bemap.PolygonStyle({ fillColor: new bemap.Color(142, 68, 173, 0.2) })
 *         });
 *         map.addPolygon(polygon);
 *     });
 *
 * @since 1.5.0
 * @public
 * @constructor
 * @extends {bemap.ServiceV2}
 * @param {bemap.Context} context
 * @param {Object} [options]
 */
bemap.EvReachableArea = function(context, options) {
    bemap.ServiceV2.call(this, context, options);
};
bemap.inherits(bemap.EvReachableArea, bemap.ServiceV2);

/**
 * Compute the reachable-area polygon.
 *
 * @public
 * @param {bemap.EvReachableAreaRequest} request
 * @param {Object} [options]
 * @param {AbortSignal} [options.signal]
 * @return {Promise<bemap.EvReachableAreaResponse>}
 */
bemap.EvReachableArea.prototype.compute = function(request, options) {
    if (!(request instanceof bemap.EvReachableAreaRequest)) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvReachableArea.compute requires a bemap.EvReachableAreaRequest'
        }));
    }
    var opts = options || {};
    var body = request.toJson();
    if (!body.geoserver) body.geoserver = this.ctx.getGeoserver();

    // Client-side validation — `== null` accepts lon=0/lat=0 (Prime Meridian / Equator).
    if (body.startLon == null || body.startLat == null) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvReachableArea.compute: request.start coordinate is required'
        }));
    }
    if (!body.vehicle) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvReachableArea.compute: request.vehicle (UUID key) is required'
        }));
    }
    if (body.temperature == null) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvReachableArea.compute: request.temperature (°C) is required by the server'
        }));
    }

    return this._post('service/evreachablearea/1.0', body, { signal: opts.signal })
        .then(function(json) {
            var resp = bemap.EvReachableAreaResponse.fromJson(json);
            if (!resp.getPolygon().length) {
                throw new bemap.Error({
                    code: bemap.Error.REACHABLE_AREA_FAILED,
                    message: 'No reachable area could be computed for the supplied vehicle / battery / temperature',
                    context: json
                });
            }
            return resp;
        });
};

/**
 * Convenience: issue N parallel `compute()` calls — one per battery level —
 * for an "overlay multiple budgets" visualisation (e.g. 50 / 70 / 90 %).
 * Returns the results in the same order as the input, each tagged with
 * the battery level used.
 *
 * @public
 * @param {bemap.EvReachableAreaRequest} request   Template — `initBatteryLevel` will be overridden.
 * @param {Array<Number>} batteryLevels            Percent values 0-100.
 * @param {Object} [options]
 * @param {AbortSignal} [options.signal]
 * @return {Promise<Array<{batteryLevel: Number, response: bemap.EvReachableAreaResponse}>>}
 */
bemap.EvReachableArea.prototype.combineMultipleBatteryLevels = function(request, batteryLevels, options) {
    if (!(request instanceof bemap.EvReachableAreaRequest)) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvReachableArea.combineMultipleBatteryLevels requires a template Request'
        }));
    }
    if (!Array.isArray(batteryLevels) || !batteryLevels.length) {
        return Promise.reject(new bemap.Error({
            code: bemap.Error.INVALID_ARGUMENT,
            message: 'EvReachableArea.combineMultipleBatteryLevels: batteryLevels must be a non-empty array'
        }));
    }
    var self = this;
    var opts = options || {};
    var promises = batteryLevels.map(function(level) {
        // Clone the request with the level swapped in.
        var cloned = new bemap.EvReachableAreaRequest({
            start: request.start,
            stop: request.stop,
            vehicle: request.vehicle,
            initBatteryLevel: level,
            temperature: request.temperature,
            payload: request.payload,
            criterias: request.criterias,
            weather: request.weather,
            weatherProvider: request.weatherProvider,
            polyline: request.polyline,
            encodedPolyline: request.encodedPolyline,
            geoserver: request.geoserver
        });
        return self.compute(cloned, { signal: opts.signal })
            .then(function(response) { return { batteryLevel: level, response: response }; });
    });
    return Promise.all(promises);
};