// constants
var TAU = 2 * Math.PI;
const EQUATORIAL_RADIUS = 6.3781370E6;
const POLAR_RADIUS = 6.3567523E6;


export function geoLatToMeters(dLat) {
    return dLat * (TAU * POLAR_RADIUS / 360);
}


export function geoLonToMeters(dLon, atLat) {
    return Math.abs(atLat) >= 90 ? 0 :
        dLon * (TAU * EQUATORIAL_RADIUS / 360) * Math.abs(Math.cos(atLat * (Math.PI / 180)));
}


export function geoMetersToLat(m) {
    return m / (TAU * POLAR_RADIUS / 360);
}


export function geoMetersToLon(m, atLat) {
    return Math.abs(atLat) >= 90 ? 0 :
        m / (TAU * EQUATORIAL_RADIUS / 360) / Math.abs(Math.cos(atLat * (Math.PI / 180)));
}


export function geoMetersToOffset(meters, tileSize) {
    tileSize = tileSize || 256;
    return [
        meters[0] * tileSize / (TAU * EQUATORIAL_RADIUS),
        -meters[1] * tileSize / (TAU * POLAR_RADIUS)
    ];
}


export function geoOffsetToMeters(offset, tileSize) {
    tileSize = tileSize || 256;
    return [
        offset[0] * TAU * EQUATORIAL_RADIUS / tileSize,
        -offset[1] * TAU * POLAR_RADIUS / tileSize
    ];
}


// Equirectangular approximation of spherical distances on Earth
export function geoSphericalDistance(a, b) {
    var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2);
    var y = geoLatToMeters(a[1] - b[1]);
    return Math.sqrt((x * x) + (y * y));
}


// scale to zoom
export function geoScaleToZoom(k, tileSize) {
    tileSize = tileSize || 256;
    var log2ts = Math.log(tileSize) * Math.LOG2E;
    return Math.log(k * TAU) / Math.LN2 - log2ts;
}


// zoom to scale
export function geoZoomToScale(z, tileSize) {
    tileSize = tileSize || 256;
    return tileSize * Math.pow(2, z) / TAU;
}


// returns info about the node from `nodes` closest to the given `point`
export function geoSphericalClosestNode(nodes, point) {
    var minDistance = Infinity, distance;
    var indexOfMin;

    for (var i in nodes) {
        distance = geoSphericalDistance(nodes[i].loc, point);
        if (distance < minDistance) {
            minDistance = distance;
            indexOfMin = i;
        }
    }

    if (indexOfMin !== undefined) {
        return { index: indexOfMin, distance: minDistance, node: nodes[indexOfMin] };
    } else {
        return null;
    }
}
