Source: bemap-services/bemap-geocoder.js

/**
 * BeNomad BeMap JavaScript API - Geocoder
 */

/**
 * @classdesc
 * Base class for geocoder.
 * @public
 * @constructor
 * @abstract
 * @param {bemap.Context} context BeMap-JS-API Context. Mandatory.
 * @param {object} options see below the available values.
 * @param {String} options.language Define the language that will be used to perform the address lookup.
 * @param {Int} options.maxResult The maximum number of items used to perform the research and returned items by the server. Default is 1.
 * @param {bemap.BoundingBox} options.boundingBox the bounding box that will limit the research to a specific area.
 */
bemap.Geocoder = function(context, options) {
  bemap.Service.call(this, context, options);

  var opts = options || {};

  /**
   * @type {String}
   * @protected
   */
  this.language = opts.language ? opts.language : null;

  /**
   * @type {int}
   * @protected
   */
  this.maxResult = opts.maxResult ? opts.maxResult : 1;

  /**
   * @type {bemap.BoundingBox}
   * @protected
   */
  this.bbox = opts.boundingBox ? opts.boundingBox : null;

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

/**
 * Get the language.
 * @return {String} language
 */
bemap.Geocoder.prototype.getLanguage = function() {
  return this.language;
};

/**
 * Get the number of max results.
 * @return {Int} maxResult
 */
bemap.Geocoder.prototype.getMaxResult = function() {
  return this.maxResult;
};

/**
 * Get the bounding box of the research.
 * @return {bemap.BoundingBox} bbox
 */
bemap.Geocoder.prototype.getBoundingBox = function() {
  return this.bbox;
};

/**
 * Set the language of the research.
 * @param {String} language the new language to set.
 * @return {bemap.Geocoder} this.
 */
bemap.Geocoder.prototype.setLanguage = function(language) {
  this.language = language;
  return this;
};

/**
 * Set the number of max results.
 * @param {Int} maxResult the new number of max results.
 * @return {bemap.Geocoder} this.
 */
bemap.Geocoder.prototype.setMaxResult = function(maxResult) {
  this.maxResult = maxResult;
  return this;
};

/**
 * Set the bounding box of the research.
 * @param {bemap.BoundingBox} bbox the new bounding box to set.
 * @return {bemap.Geocoder} this.
 */
bemap.Geocoder.prototype.setBoundingBox = function(bbox) {
  this.bbox = bbox;
  return this;
};

/**
 * Generate the BeMap request in URL encoded format.
 * @private
 * @param {object} options See below the available values.
 * @param {object} options.geoserver Geoserver name will be used for this computation.
 * @return {String} the request URL encoded.
 */
bemap.Geocoder.prototype.buildRequest = function(options) {
  var opts = options || {};
  var data = '&geoserver=' + (opts.geoserver ? opts.geoserver : this.ctx.getGeoserver());

  var searchInfo = opts.searchInfo;

  if (!searchInfo) {
    return;
  }

  for (var prop in this) {
    if (searchInfo.hasOwnProperty(prop) && searchInfo[prop]) {
      if (prop === "bbox") {
        data += "&" + prop + "=" + searchInfo[prop].minLon + "," + searchInfo[prop].minLat + "," + searchInfo[prop].maxLon + "," + searchInfo[prop].maxLat;
      } else {
        data += "&" + prop + "=" + encodeURI(searchInfo[prop]);
      }
    } else if (this[prop] && this.hasOwnProperty(prop) && prop !== 'ctx') {
      if (prop === "bbox") {
        data += "&" + prop + "=" + this[prop].minLon + "," + this[prop].minLat + "," + this[prop].maxLon + "," + this[prop].maxLat;
      } else {
        data += "&" + prop + "=" + encodeURI(this[prop]);
      }
    }
  }

  for (prop in searchInfo) {
    if (searchInfo[prop] && searchInfo.hasOwnProperty(prop)) {
      if (!this.hasOwnProperty(prop)) {
        data += "&" + prop + "=" + encodeURI(searchInfo[prop]);
      }
    }
  }

  return data;
};

/**
 * Execute the geocoding research.
 * @public
 * @param {object} options See below the available values.
 * @param {bemap.RevGeoSearchInfo} options.searchInfo the information to to search.
 * @param {object} 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.Geocoder} this.
 */
bemap.Geocoder.prototype.revGeocode = function(options) {
  //this.reset();

  var opts = options || {};

  if (!bemap.inheritsof(opts.searchInfo, bemap.RevGeoSearchInfo)) {
    console.error("SearchInfo is required!");
  }

  var i = 0;
  var first = true;
  var url = this.ctx.getBaseUrl() + 'bnd';
  var data = 'version=1.0.0&action=revgeocoding&format=json';

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

  data += this.buildRequest(opts);

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

/**
 * Send a geocoding request to the bemap's server.
 * @public
 * @param {object} options See below the available values.
 * @param {bemap.GeoSearchInfo} options.searchInfo the information to to search.
 * @param {object} 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.Geocoder} this.
 */
bemap.Geocoder.prototype.geocode = function(options) {
  //this.reset();

  var opts = options || {};

  if (!bemap.inheritsof(opts.searchInfo, bemap.GeoSearchInfo)) {
    console.error("SearchInfo is required!");
  }

  var i = 0;
  var first = true;
  var url = this.ctx.getBaseUrl() + 'bnd';
  var data = 'version=1.0.0&action=geocoding&format=json';

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

  data += this.buildRequest(opts);

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

/**
 * Excute the request by calling the BeMap server and wait the answer.
 * @private
 * @param {object} options Request options.
 * @return {bemap.Routing} this
 */
bemap.Geocoder.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.Geocoder.prototype.responseParser = function(xhr, doc, options) {
  var opts = options || {};

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

  var bnd = doc.BND;
  var elements = bnd.Elements.Element;
  var response = new bemap.GeocodingResponse();
  if (elements !== undefined) {
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      var geocodingItem = new bemap.GeocodingItem();

      for (var prop in element) {
        var elementProp = element[prop];
        if (!elementProp || !element.hasOwnProperty(prop)) {
          continue;
        }
        //add id to easly get difference between elements
        geocodingItem['index'] = i + 1;
        if (prop === 'PostalAddress') {
          var newPostalAddress = new bemap.PostalAddress({
            countryCode: elementProp.CountryCode ? elementProp.CountryCode : '',
            country: elementProp.Country ? elementProp.Country : '',
            state: elementProp.State ? elementProp.State : '',
            county: elementProp.County ? elementProp.County : '',
            city: elementProp.City ? elementProp.City : '',
            district: elementProp.District ? elementProp.District : '',
            postalCode: elementProp.PostalCode ? elementProp.PostalCode : '',
            street: elementProp.Street ? elementProp.Street : '',
            streetNumber: elementProp.StreetNumber ? elementProp.StreetNumber : ''
          });
          geocodingItem[prop].push(newPostalAddress);
        } else if (prop === 'Coordinate') {
          var newCoordinate = new bemap.Coordinate(elementProp.x, elementProp.y);
          geocodingItem[prop] = newCoordinate;
        } else if (prop === 'RoadFeature') {
          var newRoadFeature = new bemap.RoadFeature();
          for (var inf in elementProp) {
            newRoadFeature[inf] = elementProp[inf];
          }
          geocodingItem[prop].push(newRoadFeature);
        } else {
          geocodingItem[prop] = elementProp;
        }
      }

      response.geocodingItems.push(geocodingItem);
    }
  } else {
    console.error("The result of geocoding not found");
  }
  if (bnd.Extent) {
    response.extent = new bemap.BoundingBox(bnd.Extent.minX, bnd.Extent.minY, bnd.Extent.maxX, bnd.Extent.maxY);
  }
  response.action = bnd.action;
  if (opts.success) {
    opts.success(response, doc, this, xhr);
  }
};

/**
 * Create table in selected div with data from parsing. Event click send by listener
 * @public
 * @param {Object} response data for create table.
 * @param {object} options see below the available values.
 * @param {DIV} container element to create table inside.
 * @param {function} listener the function to call to get click data
 */
bemap.Geocoder.prototype.createTable = function(options, listener) {
  //create list to store created markers
  if (!bemap.markerMapObject) {
    bemap.markerMapObject = [];
  };

  if (!options) {
    console.error("Options required");
  };

  if (!options.container) {
    console.error("Container required");
  };

  if (!options.response) {
    console.error("Response required");
  };

  var container = options.container;
  var doc = options.response;

  container.empty();

  var html = '<div><table class="table table-hover table-striped">';
  html += '<thead><th>City</th><th>Country</th><th>PostalCode</th><th>Place</th></thead><tbody>';

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

    var e = doc.geocodingItems[i];

    var p = e.PostalAddress[0];
    html += '<tr class="cursorPointer" onclick="myFunction(this)" data="' + i + '">';
    html += '<td>' + (p.city && p.city !== null ? p.city : '') + '</td>';
    html += '<td>' + (p.country && p.country !== null ? p.country : '') + '</td><td>' + (p.postalCode && p.postalCode !== null ? p.postalCode : '') + '</td>';
    html += '<td>' + (p.streetNumber && p.streetNumber !== null ? p.streetNumber : '') + ' ' + (p.street && p.street !== null ? p.street : '') + '</td></tr>';
  }

  html += '</tbody></table></div>';

  var theDiv = document.getElementById(container[0].id);
  theDiv.innerHTML += html;

  //var theTr = document.querySelector('#' + container[0].id + ' tbody tr');
  //used this solution because of deleting jquery
  myFunction = function(elem) {
    var index = elem.getAttribute('data');

    var e = doc.geocodingItems[index];

    if (listener) {
      listener(e);
    };
  };
};

/**
 * Parse data to create marker or markers on map
 * @public
 * @param {bemap.Map} map for creating new marker.
 * @param {Object} response data to parse in.
 * @param {object} options see below the available values.
 * @param {function} listener the function to call to get click data
 */
bemap.Geocoder.prototype.showOnMap = function(options, listener) {

  var map = options.map ? options.map : bemap.map;
  var layer = options.layer ? options.layer : '';

  if (!options) {
    console.error("Options required");
  }

  if (!options.response) {
    console.error("Response required");
  }

  var doc = options.response;

  var icone = options.icone ? options.icone : {};

  this.cleanMarkers();
  //check if there is list of markers or only one marker
  //if list make a for loop else send directly to create marker
  if (doc.geocodingItems) {
    for (var i = 0; i < doc.geocodingItems.length; i++) {

      var e = doc.geocodingItems[i];

      this.createMarker(map, e, icone, layer, function(data) {
        if (listener) {
          listener(data);
        }
      })
    }
  } else {
    this.createMarker(map, doc, icone, layer, function(data) {
      if (listener) {
        listener(data);
      };
    });
  };
};

/**
 * Reset the geocoding marker object. Clear the previous result.
 * @public
 * @return {bemap.markerMapObject} this
 */
bemap.Geocoder.prototype.cleanMarkers = function() {
  if (bemap.markerMapObject) {
    for (i = 0; i < bemap.markerMapObject.length; i++) {
      var marker = bemap.markerMapObject[i];
      marker.remove();
      marker = undefined;
    };
    bemap.markerMapObject = [];
  } else {
    bemap.markerMapObject = [];
  };
};

/**
 * Put markers on map
 * @private
 * @param {bemap.Map} map for creating new marker.
 * @param {Object} data data to pcreate marker.
 * @param {object} icone to personalize the marker.
 * @param {function} listener the function to call to get click data
 */
bemap.Geocoder.prototype.createMarker = function(map, data, icone, layer, listener) {

  map = map ? map : bemap.map;
  icone = icone ? icone : {};

  var posx = data.Coordinate.lon;
  var posy = data.Coordinate.lat;
  var c = new bemap.Coordinate(posx, posy);
  var icon = new bemap.Icon({
    src: icone.src ? icone.src : console.error("Check your icon adress REQUIRED ICON"),
    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
  });
  //before creating new markers check if there was already created
  //this condition was made because of two posibilities of creation the markers
  //first by list second one by one
  if (bemap.markerMapObject) {
    for (var i = 0; i < bemap.markerMapObject.length; i++) {
      var mark = bemap.markerMapObject[i];
      if (mark.id == data.index) {
        map.move(posx, posy, 16);
        return
      };
    };
  };

  var marker = new bemap.Marker(
    c, {
      properties: data,
      icon: icon,
      id: data.index
    }
  );

  if (layer) {
    map.addMarker(marker, {
      layer: layer
    });
  } else {
    map.addMarker(marker);
  };
  //save list of markers to clean it later or to prevent dubbles
  bemap.markerMapObject.push(marker);

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