Source: bemap-services-v2/charging-stations/bemap-charging-station-search-request.js

/**
 * BeNomad BeMap JavaScript API - ChargingStationSearchRequest (v1.5)
 *
 * Strongly-typed request builder for `POST service/chargingstation/search/1.0`.
 *
 * Wire shape cross-checked against the BeMap server contract in
 * `bemap_idea/.../service/v1_0_0/model/chargingStation/ChargingStationSearchRequest.java`
 * and verified live on `bemap-beta.benomad.com`.
 *
 * Required: at least one provider name (`providers`) AND one spatial
 * selector — `coordinate` + `radius`, `corridor` + `radius`, or `boundingBox`.
 *
 * Example — circular search around Paris:
 *
 *     var req = new bemap.ChargingStationSearchRequest({
 *         providers: ['ecoMovement'],
 *         coordinate: new bemap.Coordinate(2.35, 48.85),
 *         radius: 2000,
 *         maxPoolResult: 25,
 *         options: [bemap.ChargingStationOption.PATH_POOL_MAP]
 *     });
 *
 * @since 1.5.0
 * @public
 * @constructor
 * @param {Object} [options]
 * @param {bemap.ChargingStationMode} [options.mode=LOCAL_OR_REMOTE]
 * @param {Array.<bemap.ChargingStationOption>} [options.options]
 * @param {Number} [options.pathAutoMaxPool=20]
 *        When the `PATH_AUTO` option is used, drop to point-level depth as
 *        soon as the result count is below this threshold.
 * @param {Array.<String>} options.providers
 *        Provider names (e.g. `'ecoMovement'`). Only names exposed by the
 *        current user's ACL are accepted server-side.
 * @param {bemap.Coordinate|bemap.CoordinateSat|{lon:Number, lat:Number}} [options.coordinate]
 *        Search centre. Required with `radius` unless `corridor` or `boundingBox` is set.
 * @param {Array.<bemap.Coordinate>} [options.corridor]
 *        Polyline corridor around which to search. Mutually exclusive with
 *        `coordinate` and `boundingBox`.
 * @param {Number} [options.radius]
 *        Search radius in meters. Required with `coordinate` or `corridor`.
 * @param {bemap.BoundingBox} [options.boundingBox]
 *        Rectangular search window. Mutually exclusive with `coordinate` and `corridor`.
 * @param {Number} [options.maxPoolResult]   Max pools in the response.
 * @param {Number} [options.maxProviderResult] Max items the server fetches from each provider.
 * @param {Array.<Number>} [options.connectorIdFilters] Connector type IDs to keep.
 * @param {String} [options.poolIdFilter]    Restrict result to a single pool by ID.
 * @param {String} [options.stationIdFilter] Restrict result to a single station by ID.
 * @param {String} [options.pointIdFilter]   Restrict result to a single charging point by ID.
 * @param {Number} [options.filtersVersion=1] Filter engine version. Server default is 1.
 * @param {Array.<String>} [options.filters] Filter expressions — see BeMap docs.
 * @param {String} [options.language]        ISO 639-1 language for address fields.
 * @param {String} [options.geoserver]       Override the Context's geoserver for this call.
 */
bemap.ChargingStationSearchRequest = function(options) {
    var opts = options || {};
    this.mode = opts.mode || bemap.ChargingStationMode.LOCAL_OR_REMOTE;
    this.options = Array.isArray(opts.options) ? opts.options.slice() : null;
    this.pathAutoMaxPool = (typeof opts.pathAutoMaxPool === 'number') ? opts.pathAutoMaxPool : 20;
    this.providers = Array.isArray(opts.providers) ? opts.providers.slice() : null;
    this.coordinate = opts.coordinate || null;
    this.corridor = Array.isArray(opts.corridor) ? opts.corridor.slice() : null;
    this.radius = (typeof opts.radius === 'number') ? opts.radius : 0;
    this.boundingBox = opts.boundingBox || null;
    this.maxPoolResult = (typeof opts.maxPoolResult === 'number') ? opts.maxPoolResult : 0;
    this.maxProviderResult = (typeof opts.maxProviderResult === 'number') ? opts.maxProviderResult : 0;
    this.connectorIdFilters = Array.isArray(opts.connectorIdFilters) ? opts.connectorIdFilters.slice() : null;
    this.poolIdFilter = opts.poolIdFilter || null;
    this.stationIdFilter = opts.stationIdFilter || null;
    this.pointIdFilter = opts.pointIdFilter || null;
    this.filtersVersion = (typeof opts.filtersVersion === 'number') ? opts.filtersVersion : 1;
    this.filters = Array.isArray(opts.filters) ? opts.filters.slice() : null;
    this.language = opts.language || null;
    this.geoserver = opts.geoserver || null;
};

/**
 * Tiny module-private serialisation helpers for `Coordinate` / `BoundingBox`.
 * Not attached to the public `bemap` namespace — internal to this file.
 * @private
 */
bemap.ChargingStationSearchRequest._coordToJson = function(c) {
    if (!c) return null;
    var lon = (typeof c.getLon === 'function') ? c.getLon() : (c.lon !== undefined ? c.lon : c.longitude);
    var lat = (typeof c.getLat === 'function') ? c.getLat() : (c.lat !== undefined ? c.lat : c.latitude);
    return { lon: lon, lat: lat };
};
bemap.ChargingStationSearchRequest._bboxToJson = function(b) {
    if (!b) return null;
    if (typeof b.getMinLon === 'function') {
        return {
            topLeft: { lon: b.getMinLon(), lat: b.getMaxLat() },
            bottomRight: { lon: b.getMaxLon(), lat: b.getMinLat() }
        };
    }
    return b;
};

/**
 * Serialise the request to the JSON shape consumed by the BeMap server.
 * Null / empty fields are dropped so the payload stays minimal.
 *
 * @public
 * @return {Object}
 */
bemap.ChargingStationSearchRequest.prototype.toJson = function() {
    var body = { mode: this.mode };
    var coordToJson = bemap.ChargingStationSearchRequest._coordToJson;
    var bboxToJson  = bemap.ChargingStationSearchRequest._bboxToJson;

    if (this.options && this.options.length) body.options = this.options.slice();
    // Only suppress the default (20). Explicit `0` MUST reach the wire so
    // it can mean "never auto-switch" per server semantics.
    if (this.pathAutoMaxPool !== 20) body.pathAutoMaxPool = this.pathAutoMaxPool;
    if (this.providers && this.providers.length) body.providers = this.providers.slice();
    if (this.coordinate) body.coordinate = coordToJson(this.coordinate);
    if (this.corridor && this.corridor.length) body.corridor = this.corridor.map(coordToJson);
    if (this.radius > 0) body.radius = this.radius;
    if (this.boundingBox) body.bbox = bboxToJson(this.boundingBox);
    if (this.maxPoolResult > 0) body.maxPoolResult = this.maxPoolResult;
    if (this.maxProviderResult > 0) body.maxProviderResult = this.maxProviderResult;
    if (this.connectorIdFilters && this.connectorIdFilters.length) body.connectorIdFilters = this.connectorIdFilters.slice();
    if (this.poolIdFilter) body.poolIdFilter = this.poolIdFilter;
    if (this.stationIdFilter) body.stationIdFilter = this.stationIdFilter;
    if (this.pointIdFilter) body.pointIdFilter = this.pointIdFilter;
    // Same default-suppression pattern: server default is 1, only emit on override.
    if (this.filtersVersion !== 1) body.filtersVersion = this.filtersVersion;
    if (this.filters && this.filters.length) body.filters = this.filters.slice();
    if (this.language) body.language = this.language;
    if (this.geoserver) body.geoserver = this.geoserver;

    return body;
};