# Source: bemap-services/bemap-routing.js

    /**
     * BeNomad BeMap JavaScript API - Routing
     */

    /**
     * @classdesc
     * Base class for routing calculation.
     * @public
     * @constructor
     * @abstract
     * @param {bemap.Context} context BeMap-JS-API Context. Mandatory.
     * @param {object} options see below the available values.
     */
    bemap.Routing = function(context, options) {
      bemap.Service.call(this, context, options);

      var opts = options || {};

      /**
       * Native parameters of BeMap server.
       * @type {String}
       * @protected
       */
      this.nativeBeMapParams = opts.nativeBeMapParams ? opts.nativeBeMapParams : null;

      /**
       * Calculated route(s).
       * @type {bemap.Route}
       * @protected
       */
      this.routes = [];

      /**
       * Marker(s) list.
       * @type {bemap.Marker}
       * @protected
       */
      this.markerMapObject = [];

      /**
       * Vias(s) list.
       * @type {Object}
       * @protected
       */
      this.stopPoints = [];

      /**
       * ID of geometry.
       * @type {String}
       * @protected
       */
      this.geometryId = 'routePolyline';

      /**
       * Draw a polygon with the polyline array.
       * @type {boolean}
       * @protected
       */
      this.poylineAsPolygon = false;

      // Internal resource.

      this.popup = undefined;

    };
    bemap.inherits(bemap.Routing, bemap.Service);

    /**
     * Get the calculated route object.
     * @public
     * @param {integer} index of route.
     * @return {bemap.Route} the calculated route.
     */
    bemap.Routing.prototype.getRoute = function(index) {
      return this.routes[index];
    };

    /**
     * Get the calculated route object.
     * @public
     * @param {integer} index of route.
     * @return {bemap.Route} the calculated route.
     */
    bemap.Routing.prototype.getRoutes = function() {
      return this.routes;
    };

    /**
     * Get the calculated route object.
     * @public
     * @return {bemap.Route} the calculated route.
     */
    bemap.Routing.prototype.getFirstRoute = function() {
      return this.routes[0];
    };

    /**
     * Reset the Routing object. Clear the previous result.
     * @public
     * @return {bemap.Routing} this
     */
    bemap.Routing.prototype.reset = function() {
      if (this.routes) {
        for (var i = 0; i < this.routes.length; i++) {
          var route = this.routes[i];
          this.resetRoute(route);
        }
      }

      this.routes = [];

      this.stopPoints = [];

      if (this.popup) {
        this.popup.remove();
        this.popup = undefined;
      }

      //clear markers
      if (this.markerMapObject) {
        this.cleanMarkers();
      };

      return this;
    };
    /**
     * Reset the Routing object. Clear the previous result.
     * @public
     * @return {bemap.Routing} this
     */
    bemap.Routing.prototype.resetRoute = function(route) {
      var i;

      if (route.chargingStationSteps) {
        for (i = 0; i < route.chargingStationSteps.length; i++) {
          var step = route.chargingStationSteps[i];
          if (step.markerMapObject) {
            step.markerMapObject.remove();
            step.markerMapObject = undefined;
          }
        }
      }

      if (route.events) {
        for (i = 0; i < route.events.length; i++) {
          var event = route.events[i];
          if (event.geometryMapObject) {
            event.geometryMapObject.remove();
            event.geometryMapObject = undefined;
          }
        }
      }

      if (route.geometryMapObject) {
        route.geometryMapObject.remove();
        route.geometryMapObject = undefined;
      }
    };

    /**
     * Generate the BeMap request in URL encoded format.
     * @private
     * @param {object} options See below the available values.
     * @param {String} options.geoserver Geoserver name will be used for this computation.
     * @return {String} the request URL encoded.
     */
    bemap.Routing.prototype.buildRequest = function(options) {

      var opts = options || {};

      var data = '&geoserver=' + (opts.geoserver ? opts.geoserver : this.ctx.getGeoserver());

      for (i = 0; i < this.destinations.length; i++) {
        var des = this.destinations[i];
        if (des !== null && bemap.inheritsof(des, bemap.Destination)) {
          data += '&xy=' + des.getLon() + "," + des.getLat();
        } else if (des !== null && bemap.inheritsof(des, bemap.Coordinate)) {
          data += '&xy=' + des.getLon() + "," + des.getLat();
        } else {
          console.error("One of destinations is not a bemap.Destination or bemap.Coordinate object!");
          return;
        }
      }

      if (this.criterias && this.criterias !== null && this.criterias.length > 0) {
        data += '&criterias=';
        first = true;
        for (i = 0; i < this.criterias.length; i++) {
          if (first) {
            first = false;
          } else {
            data += ',';
          }
          data += this.criterias[i];
        }
      }

      if (this.departureTime > 0) {
        data += '&departureTime=' + this.departureTime;
      }

      if (this.evCnnType) {
        data += '&evCnnType=' + this.evCnnType;
      }

      if (this.evf) {
        data += '&evf=' + this.evf;
      }

      if (this.evRange) {
        data += '&evRange=' + this.evRange;
      }

      if (this.isoChroneLimit > 0) {
        data += '&isoChroneLimit=' + this.isoChroneLimit;
      }

      if (this.language) {
        data += '&language=' + this.language;
      }

      if (this.maxAlter > 0) {
        data += '&maxAlter=' + this.maxAlter;
      }

      if (this.options && this.options !== null && this.options.length > 0) {
        data += '&options=';
        first = true;
        for (i = 0; i < this.options.length; i++) {
          if (first) {
            first = false;
          } else {
            data += ',';
          }
          data += this.options[i];
        }
      }

      if (this.speed > 0) {
        data += '&speed=' + this.speed;
      }

      if (this.speedType) {
        data += '&speedType=' + this.speedType;
      }

      if (this.transportType) {
        data += '&transportType=' + this.transportType;
      }

      if (this.vf) {
        data += '&vf=' + this.vf;
      }

      if (this.xyRadius > 0) {
        data += '&xyRadius=' + this.xyRadius;
      }

      if (this.nativeBeMapParams && this.nativeBeMapParams !== null) {
        data += this.nativeBeMapParams;
      }
      return data;
    };

    /**
     * Execute the routing calculation.
     * @public
     * @param {object} options See below the available values.
     * @param {String} options.geoserver Geoserver name will be used for this computation.
     * @param {function} options.success the function to call in case of successed request.
     * @param {function} options.failed the function to call in case of failed request.
     * @return {bemap.Routing} this
     */
    bemap.Routing.prototype.compute = function(options) {
      this.reset();

      var options = options || {};

      var opts = options.request || {};

      var geoserver = options.geoserver ? options.geoserver : this.ctx.getGeoserver();

      /**
       * Destinations
       * @type {bemap.Coordinates}
       * @protected
       */
      this.destinations = opts.destinations ? opts.destinations : [];

      /**
       * Array of route Criteria.
       * @type {bemap.Criteria}
       */
      this.criterias = opts.criterias ? opts.criterias : [];

      var d = new Date();
      var t = d.getTime();
      this.departureTime = opts.departureTime > 0 ? opts.departureTime : t;

      this.evCnnType = opts.evCnnType > 0 ? opts.evCnnType : null;

      this.evf = opts.evf > 0 ? opts.evf : null;

      this.evRange = opts.evRange > 0 ? opts.evRange : null;

      this.isoChroneLimit = opts.isoChroneLimit > 0 ? opts.isoChroneLimit : 0;

      this.language = opts.language ? opts.language : "xx";

      this.maxAlter = opts.maxAlter ? opts.maxAlter : 0;

      this.options = opts.options && opts.options.length > 0 ? opts.options : ['POLYLINE'];

      this.speed = opts.speed > 0 ? opts.speed : 0;

      this.speedType = opts.speedType > 0 ? opts.speedType : null;

      this.transportType = opts.transportType ? opts.transportType : "CAR";

      this.vf = opts.vf ? opts.vf : null;

      this.xyRadius = opts.xyRadius > 0 ? opts.xyRadius : 40;

      if (this.destinations && this.destinations.length < 2) {
        console.error("Minimum of 2 destionations are required!");
        return;
      }
      if (this.destinations && this.destinations.length > 2 && this.maxAlter !== 0) {
        console.error("For alternative roads 2 destionations are required");
        return;
      }
      var i = 0;
      var first = true;
      var url = this.ctx.getBaseUrl() + 'bnd';
      var data = 'version=1.0.0&action=routing&mode=MODE_VIAS&format=json';

      if (this.ctx.isAuthInPost()) {
        data += '&' + this.ctx.getAuthUrlParams();
      } else {
        url += '?' + this.ctx.getAuthUrlParams();
      }

      data += this.buildRequest(options);

      return this.execute(url, data, options);
    };

    /**
     * Excute the request by calling the BeMap server and wait the answer.
     * @private
     * @param {object} options Request options.
     * @return {bemap.Routing} this
     */
    bemap.Routing.prototype.execute = function(url, data, options) {
      var opts = options || {};
      var _this = this;

      bemap.ajax(
        'POST',
        url,
        data,
        function(xhr, doc) {
          _this.responseParser(xhr, doc, opts);
        },
        function(xhr, doc) {
          _this.responseParser(xhr, doc, opts);
        }, {
          'requestFormat': 'urlencoded'
        }
      );

      return this;
    };

    /**
     * Convert the BeMap response to the BeMap JS API object.
     * @private
     **/
    bemap.Routing.prototype.responseParser = function(xhr, doc, options) {
      var opts = options || {};

      doc = JSON.parse(doc);
      if (!doc || this.checkErrorParser(xhr, doc, options)) {
        return;
      }

      var bnd = doc.BND;
      if (!bnd) {
        return;
      }
      this.stopPointsParser(bnd);
      this.routeParser(bnd, options);

      if (opts.success) {
        opts.success(this, bnd, doc, xhr);
      }
    };

    /**
     * Convert the BeMap route(s) to the BeMap JS API Route object(s) and save into the routes array.
     * @private
     **/
    bemap.Routing.prototype.routeParser = function(bnd, options) {
      var i = 0;
      var rts = bnd.Routes.Route;

      if (rts && rts.length > 0) {
        for (i = 0; i < rts.length; i++) {
          var r = rts[i];
          var route = new bemap.Route();

          route.length = r.Length;
          route.duration = r.Duration;
          route.averageSpeed = r.AverageSpeed;

          if (r.BoundingBox) {
            var rbbox = r.BoundingBox;
            route.extent = new bemap.BoundingBox(rbbox.minX, rbbox.minY, rbbox.maxX, rbbox.maxY);
          }

          if (r.Polyline && r.Polyline.Line && r.Polyline.Line.length > 0) {
            this.polylineParser(route, r.Polyline.Line);
          }
          if (r.Events && r.Events.length > 0) {
            this.eventsParser(route, r.Events);
          }
          this.routes.push(route);
        }
      }
    };

    bemap.Routing.prototype.stopPointsParser = function(bnd) {
      var sp = bnd.UsedDestinations.UsedDestination;

      if (sp && sp.length > 0) {
        for (i = 0; i < sp.length; i++) {
          var s = sp[i];

          s.hourMinute = this.toHourMinute(s.duration)
          s.dateString = this.msToDateString(s.duration * 1000 + this.departureTime)
          this.stopPoints.push(s);
        }
      }
    };

    /**
     * Convert the this duration to hours minutes string ex "3 h 14 min".
     * @private
     **/
    bemap.Routing.prototype.toHourMinute = function(time) {
      if (!time || time == 0) {
        return '-';
      }

      var h = parseInt(time / 3600);
      var m = String(parseInt((time / 60) % 60)).padStart(2, '0');

      var v = '';
      if (h > 0) {
        v += h + ' h ';
      }

      return v + m + ' min';
    };

    /**
     * Convert the this duration to data string ex "23/01/2020 12:52".
     * @private
     **/
    bemap.Routing.prototype.msToDateString = function(time) {
      if (!time || time == 0) {
        return '-';
      }
      var d = new Date(time);

      return String(d.getDate()).padStart(2, '0') + '/' + String(d.getMonth() + 1).padStart(2, '0') + '/' + d.getFullYear() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
    };

    /**
     * Convert the BeMap polyline to the BeMap JS API Polyline object and save into the route object.
     * @private
     **/
    bemap.Routing.prototype.polylineParser = function(route, line, options) {
      route.polyline = [];
      for (var i = 0; i < line.length; i++) {
        var pts = line[i];
        route.polyline.push(new bemap.Coordinate(pts.X, pts.Y));
      }
    };

    /**
     * Convert the events.
     * @private
     **/
    bemap.Routing.prototype.eventsParser = function(route, events, options) {
      route.events = [];
      if (!events) {
        return;
      }
      for (var i = 0; i < events.length; i++) {
        this.evtMarkersParser(route, events[i].markers, options);
      }
    };

    /**
     * Convert the markers.
     * @private
     **/
    bemap.Routing.prototype.evtMarkersParser = function(route, markers, options) {
      if (!markers) {
        return;
      }

      for (var i = 0; i < markers.length; i++) {

        this.evtEntriesParser(route, markers[i].entries, options);
      }
    };

    /**
     * Convert the entries.
     * @private
     **/
    bemap.Routing.prototype.evtEntriesParser = function(route, entries, options) {
      if (!entries) {
        return;
      }

      var routeEvent = new bemap.Route.Event();
      route.events.push(routeEvent);
      for (var i = 0; i < entries.length; i++) {
        var entry = entries[i];
        if (entry.type === 'String' && entry.name === 'countryCode' && entry.value) {
          routeEvent.countryCode = entry.value;

        } else if (entry.type === 'long' && entry.name === 'length' && entry.value) {
          routeEvent.length = entry.value;

        } else if (entry.type === 'double' && entry.name === 'duration' && entry.value) {
          routeEvent.duration = entry.value;

        } else if (entry.type === 'Geometry' && entry.name === 'polyline' && entry.value) {
          if (!routeEvent.polyline) {
            routeEvent.polyline = [];
          }

          var line = entry.value.split(' ');
          for (var j = 0; j < line.length; j++) {
            var pts = line[j].split(',');
            var x = Number(pts[0]);
            var y = Number(pts[1]);
            routeEvent.polyline.push(new bemap.Coordinate(x, y));
          }

        } else if (entry.type === 'Routesheet' && entry.name === 'routesheet' && entry.instruction) {
          this.evtEntryRoutesheetInstructionParser(routeEvent, entry.instruction, options);

        } else if (entry.type === 'ChargingStationStep' && entry.name === 'chargingStationStep') {
          this.evtEntryChargingStationStepParser(routeEvent, entry, options);
        }
      }
    };

    /**
     * Convert the entry route-sheet instruction.
     * @private
     **/
    bemap.Routing.prototype.evtEntryRoutesheetInstructionParser = function(routeEvent, instruction, options) {
      var routesheetInstruction = new bemap.Route.RoutesheetInstruction();
      var c = new bemap.Coordinate(instruction.coordinate.x, instruction.coordinate.y);
      routesheetInstruction.coordinate = c;
      bemap.fillFields(instruction, routesheetInstruction);
      routeEvent.routesheetInstruction = routesheetInstruction;
    };

    /**
     * Convert the entry ChargingStationStep.
     * @private
     **/
    bemap.Routing.prototype.evtEntryChargingStationStepParser = function(routeEvent, chargingStationStep, options) {
      var step = new bemap.ChargingStationStep();
      step.entrance = new bemap.Coordinate(chargingStationStep.longitude, chargingStationStep.latitude);
      bemap.fillFields(chargingStationStep, step);
      routeEvent.chargingStationSteps.push(step);
    };

    /**
     * Show the route(s) on map.
     * @param {bemap.Map} map the new color of text border to set.
     * @param {Object} options See below the available values.
     * @param {bemap.LineStyle} options.polylineStyle Style of line used by the renderer.
     * @param {String} options.chargingStationStepImageSrc Image path of pool stations.
     * @param {String} options.chargingStationStepNoPopup If set to true, disable the popup when the icon is clicked.
     * @param {function} options.chargingStationPopupTextCallback Function called wehen a pool statiion is clicked, the called function get the step point object and return the text of popup.
     * @return {bemap.Routing} Return this.
     */
    bemap.Routing.prototype.showOnMap = function(map, options, listener) {
      if (!this.routes) {
        return;
      }

      var opts = options || {};

      if (!opts.polylineStyle || !bemap.inheritsof(opts.polylineStyle, bemap.LineStyle)) {
        opts.polylineStyle = new bemap.LineStyle({
          width: 3,
          color: new bemap.Color(13, 80, 157, 1)
        });
      }

      if (!opts.polygonStyle || !bemap.inheritsof(opts.polygonStyle, bemap.PolygonStyle)) {
        opts.polygonStyle = new bemap.PolygonStyle({
          fillColor: new bemap.Color(255, 0, 255, 0.25),
          borderColor: new bemap.Color(255, 0, 255, 0.25),
          borderWidth: 3
        });
      }

      var red = 13;
      var green = 80;
      var blue = 157;
      for (var i = 0; i < this.routes.length; i++) {

        var color = new bemap.Color(red, green, blue)
        red = red + 50;
        green = green + 40;
        blue = blue + 30

        if (opts.changeColor) {
          opts.polylineStyle.color = color
        }

        var route = this.routes[i];
        opts.polylineId = i;

        if (this.poylineAsPolygon) {
          this.showPolygon(map, route, opts);
        } else {
          if (route.polyline) {
            this.showPolyline(map, route, opts, function(data) {
              if (listener) {
                listener(data);
              };
            });
          }
          if (route.events && route.events.length > 0) {
            this.showEventPolyline(map, route, opts, function(data) {
              if (listener) {
                listener(data);
              };
            });
          }
        }

        if (route.extent) {
          map.moveToBoundingBox(route.extent);
        }

        this.showChargingStationSteps(map, route.chargingStationSteps, opts);
      }

      return this;
    };

    /**
     * Show the destinations markers on map.
     * @public
     * @param {bemap.Map} map the new marker to set.
     * @param {Object} options See below the available values.
     * @param {bemap.Layer} options.layer Layer for markers.
     * @param {Object} options.response Optionally response from geocoding or list of coords.
     * @param {listener} listener return click on marker.
     * @return {bemap.markerMapObject} Return this.
     */
    bemap.Routing.prototype.showOnMapMarkers = function(options, listener) {

      if (!options) {
        console.error("Options required");
      };
      var map = options.map ? options.map : bemap.map;
      var layer = options.layer ? options.layer : '';
      var icone = options.icon ? options.icon : {};
      var coord = options.coord ? options.coord : {};
      var doc = {};

      if (!options.response) {
        doc.destinations = this.destinations;
      } else {
        doc = options.response;
      };

      var optionsMarker = {
        map: map,
        properties: '',
        coord: coord,
        icon: icone,
        layer: layer
      }

      this.cleanMarkers();
      //create merkers from parsed response - object UsedDestinations
      if (doc.UsedDestinations) {

        for (var i = 0; i < doc.UsedDestinations.UsedDestination.length; i++) {
          e = doc.UsedDestinations.UsedDestination[i]
          optionsMarker.properties = doc.UsedDestinations.UsedDestination[i];
          optionsMarker.coord = new bemap.Coordinate(e.usedX, e.usedY)
          this.createMarker(optionsMarker, function(data) {
            if (listener) {
              listener(data);
            };
          });
        };
      }
      //create merkers from notparsed response - object destinations
      //or from this.destinations
      else if (doc.destinations) {
        for (var i = 0; i < doc.destinations.length; i++) {
          e = doc.destinations[i]
          optionsMarker.properties = doc.stopPoints[i];
          optionsMarker.coord = new bemap.Coordinate(e.lon, e.lat)
          this.createMarker(optionsMarker, function(data) {
            if (listener) {
              listener(data);
            };
          });
        };
      } else {

        optionsMarker.coord = options.coord;
        this.createMarker(optionsMarker, function(data) {
          if (listener) {
            listener(data);
          };
        });
      };

      return this.markerMapObject;
    };

    /**
     * Clean the markerMapObject object.
     * @public
     * @return {bemap.markerMapObject} this
     */
    bemap.Routing.prototype.cleanMarkers = function() {
      if (this.markerMapObject) {
        for (i = 0; i < this.markerMapObject.length; i++) {
          var marker = this.markerMapObject[i];
          marker.remove();
          marker = undefined;
        };
        this.markerMapObject = [];
      } else {
        this.markerMapObject = [];
      };
    };
    /**
     * Show the destinations markers on map.
     * @public
     * @param {bemap.Map} options.map the new marker to set.
     * @param {Object} options See below the available values.
     * @param {listener} listener return click on marker.
     * @return {bemap.markerMapObject} Return this.
     */
    bemap.Routing.prototype.createMarker = function(options, listener) {

      var opts = options ? options : {};
      var map = opts.map ? opts.map : bemap.map;
      var properties = opts.properties ? opts.properties : opts.coord;
      var coord = opts.coord ? opts.coord : console.error("Coords required");
      var icone = opts.icon ? opts.icon : '';
      var layer = opts.layer ? opts.layer : false;
      var icon = new bemap.Icon({
        src: icone.src ? icone.src : console.error("required icon/icon adress is wrong"),
        anchorX: icone.anchorX ? icone.anchorX : 0.25,
        anchorY: icone.anchorY ? icone.anchorY : 1,
        height: icone.height ? icone.height : '',
        width: icone.width ? icone.width : '',
        anchorXUnits: icone.anchorXUnits ? icone.anchorXUnits : 'fraction',
        anchorYUnits: icone.anchorYUnits ? icone.anchorYUnits : 'fraction',
        scale: icone.scale ? icone.scale : 1
      });

      var marker = new bemap.Marker(
        coord, {
          properties: properties,
          icon: icon
        }
      );

      if (layer) {
        map.addMarker(marker, {
          layer: layer
        });
      } else {
        map.addMarker(marker);
      };

      this.markerMapObject.push(marker);

      marker.on(bemap.Map.EventType.CLICK, function(mapEvent) {
        if (listener) {
          listener(mapEvent);
        };
      });

      return marker
    };

    /**
     * Show a polyline on map
     * @private
     **/
    bemap.Routing.prototype.showPolyline = function(map, route, options, listener) {

      var opts = options || {};
      var layer = opts.layer ? opts.layer : '';
      var p = new bemap.Polyline(
        route.polyline, {
          style: opts.polylineStyle,
          id: this.geometryId + opts.polylineId
        }
      );
      route.geometryMapObject = p;

      if (layer) {
        map.addPolyline(p, {
          layer: layer
        });
      } else {
        map.addPolyline(p);
      };

      p.on(bemap.Map.EventType.CLICK, function(mapEvent) {
        if (listener) {
          listener(mapEvent);
        };
      })
    };

    /**
     * Show a polygon on map
     * @private
     **/
    bemap.Routing.prototype.showPolygon = function(map, route, options) {
      var opts = options || {};
      var p = new bemap.Polygon(
        route.polyline, {
          style: opts.polygonStyle,
          id: this.geometryId + opts.polygonId
        }
      );
      route.geometryMapObject = p;
      map.addPolygon(p);

    };

    /**
     * Show a polyline on map
     * @private
     **/
    bemap.Routing.prototype.showEventPolyline = function(map, route, options, listener) {
      var opts = options || {};
      var layer = opts.layer ? opts.layer : '';
      var events = route.events;
      for (var i = 0; i < events.length; i++) {
        var event = events[i];
        var polyline = event.polyline;

        var p = new bemap.Polyline(
          polyline, {
            style: opts.polylineStyle,
            id: this.geometryId + opts.polylineId + '#evt' + i
          }
        );
        event.geometryMapObject = p;
        if (layer) {
          map.addPolyline(p, {
            layer: layer
          });
        } else {
          map.addPolyline(p);
        };
        p.on(bemap.Map.EventType.CLICK, function(mapEvent) {
          if (listener) {
            listener(mapEvent);
          };
        })
      }
    };

    /**
     * Show a charging station step(s) on map
     * @param {object} options See below the available values.
     * @param {String} options.chargingStationStepImageSrc Image path of pool stations.
     * @param {String} options.chargingStationStepNoPopup If set to true, disable the popup when the icon is clicked.
     * @param {function} options.chargingStationPopupTextCallback Function called wehen a pool statiion is clicked, the called function get the step point object and return the text of popup.
     * @private
     **/
    bemap.Routing.prototype.showChargingStationSteps = function(map, chargingStationSteps, options) {
      if (!chargingStationSteps || chargingStationSteps.lengh == 0) {
        return;
      }

      for (var i = 0; i < chargingStationSteps.length; i++) {
        this.showChargingStationStep(map, chargingStationSteps[i], options);
      }
    };

    /**
     * Show a charging station step on map
     * @private
     **/
    bemap.Routing.prototype.showChargingStationStep = function(map, step, options) {
      if (!step.entrance) {
        return;
      }

      var _this = this;
      var src = options.chargingStationStepImageSrc ? options.chargingStationStepImageSrc : 'images/map-marker-red.svg';
      var marker = new bemap.Marker(
        step.entrance, {
          icon: new bemap.Icon({
            src: src,
            anchorX: options.chargingStationStepImageAnchorX ? options.chargingStationStepImageAnchorX : 0.50,
            anchorY: options.chargingStationStepImageAnchorY ? options.chargingStationStepImageAnchorY : 1,
            anchorXUnits: 'fraction',
            anchorYUnits: 'fraction'
          })
        });
      map.addMarker(marker);

      step.markerMapObject = marker;

      if (!options.chargingStationStepNoPopup) {
        marker.on(bemap.Map.EventType.CLICK, function() {
          _this.popupChargingStationStep(map, step, options);
        });
      }
    };

    /**
     * Show a charging station step on map
     * @param {object} options See below the available values.
     * @param {function} options.chargingStationPopupTextCallback Function called wehen a pool statiion is clicked, the called function get the step point object and return the text of popup.
     * @private
     **/
    bemap.Routing.prototype.popupChargingStationStep = function(map, step, options) {
      if (!this.popup) {
        this.popup = new bemap.Popup({
          coordinate: step.entrance,
          visible: false
        });
        map.addPopup(this.popup);
      }

      var html;
      if (options.chargingStationPopupTextCallback) {
        html = options.chargingStationPopupTextCallback(step);

      } else {
        html = '<p>';

        if (step.id) {
          html += 'Id: ' + step.id + '<br/>';
        }

        if (step.entrance) {
          var entrance = step.entrance;
          html += 'Latitude, longitude: ' + entrance.getLat() + ', ' + entrance.getLon() + '<br/>';
        }

        if (step.country || step.city || step.street) {
          html += 'Address:<br/>';

          if (step.streeNumber && step.street) {
            html += step.streeNumber + ' ' + step.street + '<br/>';
          } else {
            if (step.streeNumber) {
              html += step.streeNumber;
            }
            if (step.street) {
              html += step.street;
            }
            html += '<br/>';
          }

          if (step.postalCode && step.city) {
            html += step.postalCode + ' ' + step.city + '<br/>';
          } else {
            if (step.postalCode) {
              html += step.postalCode;
            }
            if (step.city) {
              html += step.city;
            }
            html += '<br/>';
          }

          if (step.country) {
            html += step.country + '<br/>';
          }
        }

        if (step.batteryChargeLevel) {
          html += 'Battery: ' + step.batteryChargeLevel + '%<br/>';
        }
        if (step.chargingTime) {
          html += 'Charging time: ' + step.chargingTime + 'ms<br/>';
        }
        if (step.consumedFromPreviousStop) {
          html += 'Consumed: ' + step.consumedFromPreviousStop + 'kW<br/>';
        }
        if (step.maxNominalPower) {
          html += 'Maximum power: ' + step.maxNominalPower + 'kW<br/>';
        }
        if (step.numberOfChargingPoint) {
          html += 'Number of charge points: ' + step.numberOfChargingPoint + '<br/>';
        }

        html += '</p>';
      }

      this.popup.setInformation(html);
      this.popup.setCoordinate(step.entrance).show();
    };