Source: Map/GnafAddressGeocoder.js

'use strict';

/*global require*/
var VarType = require('../Map/VarType');
var AddressGeocoder = require('../Map/AddressGeocoder');
var BulkAddressGeocoderResult = require('../Map/BulkAddressGeocoderResult');
var DeveloperError = require('terriajs-cesium/Source/Core/DeveloperError');
var GnafApi = require('../Models/GnafApi');
var defined = require('terriajs-cesium/Source/Core/defined');
var JulianDate = require('terriajs-cesium/Source/Core/JulianDate');

/**
 * Australia-only address converter, which uses the GNAF api to determine lat and long coordinates for a given address.
 * If TableStructure has address column, adds two new columns: lat and long.
 *
 * @alias GnafAddressGeocoder
 * @constructor
 * @extends AddressGeocoder
 *
 */
var GnafAddressGeocoder = function() {
    AddressGeocoder.call(this);
};

/**
 * Convert addresses in miniumum number of calls to the server. When the promise fulfills, the tableStructure has been
 * updated with new lat and lon columns.
 *
 * @param {TableStructure} [tableStructure] A tableStructure that contains an address column.
 * @param {CorsProxy} [corsProxy] Proxy for cross origin resource sharing
 * @return {Promise} Promise that resolves to an BulkAddressGeocoderResult object.
 */
GnafAddressGeocoder.prototype.bulkConvertAddresses = function(tableStructure, corsProxy) {
    this.startTime = JulianDate.now();
    this.tableStructure = tableStructure;
    if (!tableStructure.hasAddress) {
        throw new DeveloperError('This tableStructure has no addresses!');
    }

    var addressesCol = tableStructure.columnsByType[VarType.ADDR][0];
    if (addressesCol.length === 0) {
        throw new DeveloperError('Even though the tableStructure reports it has an address column, '
                                 + 'it has no addresses!');
    }
    var suburbs = tableStructure.getColumnWithName("Suburb");
    smooshColumn(addressesCol, suburbs);
    var state = tableStructure.getColumnWithName("State");
    smooshColumn(addressesCol, state);
    var postCodeCol = tableStructure.getColumnWithName("Postcode");
    smooshColumn(addressesCol, postCodeCol);
    var gnafApi = new GnafApi(corsProxy);

    var addressesPlusInd = prefilterAddresses(addressesCol.values);
    this.skipIndices = addressesPlusInd.skipIndices;
    this.addressesCol = addressesCol;
    this.numberOfAddressesConverted = addressesPlusInd.addresses.length;
    var that = this;

    return gnafApi.bulkGeoCode(addressesPlusInd.addresses, undefined).then(function(info) {
        var longValues = [];
        var latValues = [];
        var matchedAddresses = [];
        var resultScores = [];
        var missingAddresses = [];
        var j = 0;
        for (var i=0; i<that.addressesCol.values.length; i++) {
            if (that.skipIndices.indexOf(i) !== -1
                    || !defined(info[j])
                    || !defined(info[j].location)
                    || isNaN(info[j].location.longitude)
                    || isNaN(info[j].location.latitude)) {
                if (that.addressesCol.values[i] !== null) {
                    missingAddresses.push(that.addressesCol.values[i]);
                }
                longValues.push(null);
                latValues.push(null);
                resultScores.push(null);
                matchedAddresses.push(null);
                continue;
            }
            longValues.push(info[j].location.longitude);
            latValues.push(info[j].location.latitude);
            resultScores.push(info[j].score);
            matchedAddresses.push(info[j].name);
            j++;
        }
        that.tableStructure.addColumn("Matched Address", matchedAddresses);
        that.tableStructure.addColumn("Lon", longValues);
        that.tableStructure.addColumn("Lat", latValues);
        that.tableStructure.addColumn("Score", resultScores);
        var addressGeocoderData = new BulkAddressGeocoderResult(that.startTime,
                that.numberOfAddressesConverted,
                addressesPlusInd.nullAddresses,
                missingAddresses);
        return addressGeocoderData;
    });
};

/**
 * Add info to address, comma separated.
 * @param {TableColumn} [addressesCol] Table column with addresses to be added to
 * @param {TableColumn} [newCol] Table column with extra info to be added to addresses column
 *
 * @private
 */
function smooshColumn(addressesCol, newCol) {
    if (!defined(newCol)) {
        return;
    }
    if (newCol.values.length === addressesCol.values.length) {
        for (var i=0; i<addressesCol.values.length; i++) {
            if (addressesCol.values[i] !== null && newCol.values[i] !== null) {
                addressesCol.values[i] = addressesCol.values[i] + " " + newCol.values[i];
            }
        }
    }
}

/**
 * Do not try to geocode addresses that don't look valid.
 *
 * @param {Array} addressList List of addresses that will be considered for geocoding
 * @return {Object} Probably shorter list of addresses that should be geocoded, as well as indices of addresses that
 *                  were removed.
 * @private
 */
function prefilterAddresses(addressList) {
    var addressesPlusInd = {skipIndices: [],
                            nullAddresses: 0,
                            addresses: []};

    for (var i=0; i<addressList.length; i++)
    {
        var address = addressList[i];
        if (address === null) {
            addressesPlusInd.skipIndices.push(i);
            addressesPlusInd.nullAddresses++;
            continue;
        }
        if (address.toLowerCase().indexOf("po box") !== -1 ||
            address.toLowerCase().indexOf("post office box") !== -1) {
            addressesPlusInd.skipIndices.push(i);
            continue;
        }
        addressesPlusInd.addresses.push(address);
    }
    return addressesPlusInd;
}

module.exports = GnafAddressGeocoder;