Source: bu-street/transform.js

goog.require('bu');

/**
 * Transforms a coordinate from phi/theta to pitch/yaw inside an
 * equirectangular panorama 360 image.
 * @param {bu.Coordinate} coordinate Coordinate as [phi, theta].
 * @param {bu.street.Image} image Image object to which transform.
 * @return {bu.Coordinate} Coordinate transformed to [pitch, yaw].
 * @author josea.hernandez@blom.no (Jose Antonio Hernandez)
 * @api
 */
bu.street.phiThetaToPitchYaw = function(coordinate, image) {
    var phi = coordinate[0];
    var theta = coordinate[1];
    var pitch = - ( (image.equi_img_width /2) -phi - image.row_center ) * 
        bu.M_DEG_TO_RAD / image.pixel_angle;
    var yaw = ( theta - image.column_center )  * bu.M_DEG_TO_RAD / image.pixel_angle;
    return [pitch, yaw];
};

/**
 * Transforms a coordinate from pitch/yaw to phi/theta inside an
 * equirectangular panorama 360 image.
 * @param {bu.Coordinate} coordinate Coordinate as [pitch, yaw].
 * @param {bu.street.Image} image Image object to which transform.
 * @return {bu.Coordinate} Coordinate transformed to [phi, theta].
 * @api
 */
bu.street.pitchYawToPhiTheta = function(coordinate, image) {
    var pitch = coordinate[0];
    var yaw = coordinate[1];
    var phi = (image.equi_img_width /2) + (pitch * bu.M_RAD_TO_DEG * image.pixel_angle) - image.row_center;  
    var theta = ( yaw * bu.M_RAD_TO_DEG * image.pixel_angle ) + image.column_center;
    if ( theta < 0 ) theta = image.equi_img_width + theta;
    if ( theta > image.equi_img_width ) theta = theta - image.equi_img_width;
    return [phi, theta];
};



/**
 * Transforms a coordinate from longitude/latitude/z to pitch/yaw inside an
 * equirectangular panorama 360 image.
 * @param {bu.Coordinate} coordinate Coordinate as longitude/latitude/z, i.e.
 *     an array with longitude as 1st and latitude as 2nd element, and z is
 *     elevation in meters. If this value is not known it can be ignored and 
 *     use only a coordinate of two values to get a good aproximation.
 * @param {bu.street.Image} image Image object to which transform.
 * @return {bu.Coordinate} Coordinate transformed where first value is pitch and
 *      second value is yaw in radians. Yaw is [0, 2PI) value where zero is at 
 *      initial direction and pitch is [+PI/2,-PI/2].
 * @api
 */
bu.street.fromLonLatToPitchYaw = function(coordinate, image) {
    var direction_yaw_rad = image.direction_yaw * Math.PI / 180.0;

    var lon_rad = coordinate[0] * Math.PI / 180.0;
    var lat_rad = coordinate[1] * Math.PI / 180.0;
    var z = coordinate[2];
    var imagelon_rad = image.xcp * Math.PI / 180.0;
    var imagelat_rad = image.ycp * Math.PI / 180.0;
    var imagez = image.zcp;
    var h = image.camera_height;
    
    var deltalon = (lon_rad - imagelon_rad); //Increment in longitude (radians)
    //beta = Angle or azimuth between north and direction to world point (radians)
    //atan2 function retrieves values between -pi and pi. Angles are taken 
    //clockwise starting at north, which is value zero
    var beta = Math.atan2(Math.sin(deltalon) * Math.cos(lat_rad), 
        Math.cos(imagelat_rad) * Math.sin(lat_rad) - 
        Math.sin(imagelat_rad) * Math.cos(lat_rad) * Math.cos(deltalon));
    //yaw = Angle or azimuth between vehicle direction and direction of line 
    //  between image shot point and world point (radians)
    var yaw = beta - direction_yaw_rad;
    //Ensure yaw is in 0, 2π range
    var dPI = 2 * Math.PI;
    if (yaw < 0.0) yaw += dPI;
    //JS does not have decimal modulus and next line do not work
    //yaw = yaw % dPI;
    yaw = (+yaw % (dPI = +dPI) + dPI) % dPI;

    var R = bu.MEAN_EARTH_RADIUS;
    
    //pitch = Polar angle between Z axis and direction to world point
    //alpha = angle between vectors of both points respect earth center
    //Some equations taken from :
    //  http://www.ajdesigner.com/phptriangle/scalene_triangle_area_height.php
    //  http://mathworld.wolfram.com/GreatCircle.html
    var alpha = Math.acos(Math.cos(lat_rad) * Math.cos(imagelat_rad) * Math.cos(deltalon)
        + Math.sin(lat_rad) * Math.sin(imagelat_rad));
    var b = R + imagez;
    var a = b * Math.tan(alpha);
    var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    var d = c - R - z;
    var f = c - d - h;
    var e = Math.sqrt(Math.pow(b, 2) + Math.pow(f, 2) - 2 * b * f * Math.cos(alpha));
    var pitch = Math.acos((Math.pow(a, 2) + Math.pow(e, 2) - Math.pow(d + h, 2)) / (2*a*e));
    
    //If we consider the Earth a sphere we can approach the calculation with this equation:
    // tan(PI - pitch) = (R + z2) * sin(a) / (h + z1 + R - (R + z2) * cos(a))
    //var distance = bu.street.calculateDistance([image.xcp, image.ycp], coordinate);
    //var a = distance / R;
    //var pitch = Math.PI - Math.atan2(
    //    (R + z) * Math.sin(a), 
    //    image.camera_height + image.zcp + R - (R + z) * Math.cos(a)
    //);
    return [pitch, yaw];
};

/**
 * Transforms a coordinate from a local coordinate XYZ to phi/theta inside an
 * equirectangular panorama 360 image.
 * @param {bu.Coordinate} coordinate Coordinate as XYZ in the local projection
 *     defined for the image.
 * @param {bu.street.Image} image Image object to which transform.
 * @return {bu.Coordinate} Coordinate transformed where first value is phi and
 *      second value is theta.
 * @api
 */
bu.street.fromLocalCoordToPhiTheta = function(coordinate, image) {
    var phi, theta;
    var r,u,v,w,ang1,vx1,vy1;
    var pixel_angle= image.equi_img_width/360.0 ;
    var xcp = image.local_xcp;
    var ycp = image.local_ycp;
    var zcp = image.zcp;
    
    var x = coordinate[0];
    var y = coordinate[1];
    var z = coordinate[2];
    
    u = image.ext0*(x-xcp) + image.ext3*(y-ycp) + image.ext6*(z-zcp) + xcp;
    v = image.ext1*(x-xcp) + image.ext4*(y-ycp) + image.ext7*(z-zcp) + ycp;
    w = image.ext2*(x-xcp) + image.ext5*(y-ycp) + image.ext8*(z-zcp) + zcp;
    
    r= Math.sqrt( (u-xcp) * (u-xcp) + (v-ycp) * (v-ycp) + (w-zcp) * (w-zcp) );
    
    vx1=u-xcp;
    vy1=v-ycp;
    
    ang1 = -Math.atan2(vy1,vx1) * bu.M_RAD_TO_DEG;
    theta = ( ang1 * pixel_angle ) + image.column_center;
    if(theta < 0 ) theta = image.equi_img_width + theta;
    if(theta > image.equi_img_width ) theta = theta - image.equi_img_width;
    
    phi = Math.asin((w - zcp) / r )* bu.M_RAD_TO_DEG;
    phi = (image.equi_img_width /2) - ( phi * pixel_angle ) - image.row_center;
    return [phi, theta];
};

/**
 * Transforms an array of coordinates from a local coordinate XYZ to phi/theta inside an
 * equirectangular panorama 360 image.
 * @param {Array.<bu.Coordinate>} coordinates Array of coordinate as XYZ in the local projection
 *     defined for the image.
 * @param {bu.street.Image} image Image object to which transform.
 * @return {Array.<bu.Coordinate>} Array of coordinate transformed where first value is phi and
 *      second value is theta.
 * @api
 */
bu.street.fromLocalCoordsToPhiTheta = function(coordinates, image) {
    var phi, theta;
    var r,u,v,w,ang1,vx1,vy1;
    var pixel_angle= image.equi_img_width/360.0 ;
    var xcp = image.local_xcp;
    var ycp = image.local_ycp;
    var zcp = image.zcp;
    
    var result = new Array(coordinates.length,3);
    for( i = 0;i<coordinates.length;i++)
    {
        var x = coordinates[i][0];
        var y = coordinates[i][1];
        var z = coordinates[i][2];
        
        u = image.ext0*(x-xcp) + image.ext1*(y-ycp) + image.ext2*(z-zcp) + xcp;
        v = image.ext3*(x-xcp) + image.ext4*(y-ycp) + image.ext5*(z-zcp) + ycp;
        w = image.ext6*(x-xcp) + image.ext7*(y-ycp) + image.ext8*(z-zcp) + zcp;
    
        r= Math.sqrt( (u-xcp) * (u-xcp) + (v-ycp) * (v-ycp) + (w-zcp) * (w-zcp) );
    
        vx1=u-xcp;
        vy1=v-ycp;
    
        ang1 = -Math.atan2(vy1,vx1) * bu.M_RAD_TO_DEG;
        theta = ( ang1 * pixel_angle ) + image.column_center;
        if(theta < 0 ) theta = image.equi_img_width + theta;
        if(theta > image.equi_img_width ) theta = theta - image.equi_img_width;
        
        phi = Math.asin((w - zcp) / r )* bu.M_RAD_TO_DEG;
        phi = (image.equi_img_width /2) - ( phi * pixel_angle ) - image.row_center;
        result[i] = [phi, theta];
    }
    return result;
};

/**
 * Calculates distance in meters from one point to other of Earth. Calculation is 
 *      aproximated using mean radius spherical Earth.
 * @param {bu.Coordinate} coordinate1 Coordinate as longitude and latitude, i.e.
 *     an array with longitude as 1st and latitude as 2nd element.
 * @param {bu.Coordinate} coordinate2 Coordinate as longitude and latitude, i.e.
 *     an array with longitude as 1st and latitude as 2nd element.
 * @return {number} Distance in meters.
 * @api
 */
bu.street.calculateDistance = function(coordinate1, coordinate2)
{
	var lon1_rad = coordinate1[0] * Math.PI / 180.0;
	var lat1_rad = coordinate1[1] * Math.PI / 180.0;
	var lon2_rad = coordinate2[0] * Math.PI / 180.0;
	var lat2_rad = coordinate2[1] * Math.PI / 180.0;
	var A = Math.sin(lat1_rad);
	var B = Math.sin(lat2_rad);
	var C = Math.cos(lat1_rad);
	var D = Math.cos(lat2_rad);
	var E = Math.cos(Math.abs(lon1_rad - lon2_rad));
	return Math.acos(A*B + C*D*E) * bu.MEAN_EARTH_RADIUS;
};

/**
 * Transforms one coordinate in geographical projection to spherical mercator.
 * @param {number} longitude Longitude of the coordinate we want to transform.
 * @param {number} latitude Longitude of the coordinate we want to transform.
 * @return {bu.Coordinate} Coordinate as longitude and latitude.
 * @api 
 */
bu.street.latLon2MercatorSpheric = function (longitude, latitude){
    var dblLatitude;
    var dblLongitude;
    var dblLambda;
    var dblPhi;
    
    dblLongitude = longitude;	// 100000;
    dblLatitude = latitude;		// 100000
    dblLambda = dblLongitude;
    dblPhi = dblLatitude;
    dblLongitude = bu.MEAN_EARTH_RADIUS * ((Math.PI / 180) * ((dblLambda) - 0.0));
    dblLatitude = bu.MEAN_EARTH_RADIUS * Math.log(Math.tan((Math.PI / 4) + (Math.PI / 180) * dblPhi * 0.5));
    return [dblLongitude, dblLatitude];
};

/**
 * Transforms one coordinate in spherical mercator projection to geographical.
 * @param {number} longitude Longitude of the coordinate we want to transform.
 * @param {number} latitude Longitude of the coordinate we want to transform.
 * @return {bu.Coordinate} Coordinate as longitude and latitude.
 * @api 
 */
bu.street.mercatorSpheric2LatLon = function (longitude, latitude){
    var lon = longitude * Math.PI / 180;
    var lat = latitude * Math.PI / 180;
    var e1 = 0.081819;
    var iso = Math.log(Math.tan((lat / 2.0) + (Math.PI / 4.0))) - (e1 / 2.0 * Math.log((1.0 + e1 * Math.sin(lat)) / (1.0 - e1 * Math.sin(lat))));
    var longitudeResult = bu.MEAN_EARTH_RADIUS * (lon);
    var latitudeResult = bu.MEAN_EARTH_RADIUS * iso;
    return [longitudeResult, latitudeResult];
};