/**
 * @author akopinga
 * Opmerking: zorg ervoor dat er een JSON-parser beschikbaar is (routine transformGJString).
 */

//---------- hulpobject: conversie rad <> deg -----------
var helper = {
	rad : function(hoek_deg) { return Math.PI*hoek_deg/180.0; },
	deg : function(hoek_rad) { return 180.0*hoek_rad/Math.PI; }
};

//-------- projectieparameters --------
//T1: korte zijde trapezium
//T2: lange zijde trapezium
//Torig: vec2 met oorsprong trapezium (= midden van de korte zijde)
//tau: hoek tussen verticaal van trapezium en de noord-as op de kaart, in radialen.
//ImW: breedte foto,in pixels.
//ImH: hoogte foto, in pixels.
//beta: kijkhoek camera
//H: vlieghoogte in map-eenheden (!)
//method:
//	0 --> beta gegeven, H berekend
//	<>0 --> H gegeven, beta berekend
// TODO: aanpassing v5 -> v6 : functie getDx toevoegen om correctie binnen trapeziumstelsel te kunnen maken
//              nodig: oude versie map_to_trap en (private) property dxArray
function proj_params (poly_simple, T1, T2, Torig, tau, ImW, ImH, beta, H, method)
{
	var i; //ye olde loop variable
    
    //inputparameters
    this.T1 = T1;
	this.T2 = T2;
	this.Torig = Torig;
	this.tau = tau;
	this.ImW = ImW;
	this.ImH = ImH;
	
	this.aspect = 1.0*ImW/ImH;
	
	if (method==0) 
	{
		this.beta = beta;
		this.H = (Math.sin(beta)/this.aspect) * (T2*T1) / (T2-T1);
	}
	else 
	{
		this.beta = Math.asin(this.aspect * H * (T2-T1) / (T2*T1));
		this.H = H;
	};
	
	//afgeleide parameters
	this.alfav = Math.atan(Math.tan(0.5*Math.PI - this.beta) * (T2-T1)/(T2+T1));
	this.alfah = Math.atan(this.aspect * Math.tan(this.alfav));
    this.THeight = this.H * (Math.tan(this.beta + this.alfav) - Math.tan(this.beta - this.alfav)); //nieuw in v6
    
    //modeltrapezium waar alle berekeningen op gebaseerd zijn
    var model = new Array();
    model.push(new vec2(-0.5 * T1, 0.0));
    model.push(new vec2(0.5 * T1, 0.0));
    model.push(new vec2(-0.5 * T2, this.THeight));
    model.push(new vec2(0.5 * T2, this.THeight));
    
    //invoertrapezium geroteerd en verplaatst naar trapeziumstelsel
    var trap_real = new Array();
    for (i=0; i < poly_simple.length; i++) {
      	var T = new vec2(-Torig.X, -Torig.Y);
		trap_real.push(poly_simple[i].translate(T).rotate(tau));	
    };

    //vector uit arr die het dichtst bij V ligt
    function closest(V,arr) {
        var mindist = V.dist(arr[0]);
        var minvec = arr[0];
        var i;
        
        for (i=1; i<arr.length; i++) {
            var disti = V.dist(arr[i]);
            if (disti < mindist) {
                mindist = disti;
                minvec = arr[i];
            }
        }
        
        return minvec;
    };

    //Array met verschilvectoren tussen invoertrapezium en modeltrapezium
    this.dXarr = new Array();
    for (i=0; i<4; i++) {
        var Vc = closest(model[i], trap_real); //de hoek van het invoerpolygoon die het dichtst bij de corresponderende hoek van het modelpolygoon ligt
        var rX = Vc.X;
        var rY = Vc.Y;
        var mX = model[i].X;
        var mY = model[i].Y;
        
        this.dXarr.push(new vec2(rX - mX, rY - mY));
    }
    
    //correctiefunctie -> geeft correctievector op punt (xp,yp) binnen trapeziumstelsel
    this.getDx = function (Vp) {
        var yh = Vp.Y / this.THeight;
        var xh = 0.5 + Vp.X / (T1 * (1.0-yh) + T2 * yh);
        var pt1 = this.dXarr[0].scale(1.0-xh).translate(this.dXarr[1].scale(xh));
        var pt2 = this.dXarr[2].scale(1.0-xh).translate(this.dXarr[3].scale(xh));
        
        return pt1.scale(1.0-yh).translate(pt2.scale(yh));
    };

    //Voor inspectiedoeleinden
	this.toString = function() { //TODO: dxArray toevoegen?
		var S = "";
		S += "alfav --> " + helper.deg(this.alfav) + " deg.\n";
		S += "beta  --> " + helper.deg(this.beta) + " deg.\n";
		S += "tau   --> " + helper.deg(this.tau) + " deg.\n";
		S += "H     --> " + H + " map-units.\n";
        S += "dXarr --> " + this.dXarr;
        return S;		
	};
};

//------------------- een vector ------------------------
function vec2 (X,Y) 
{
	this.X = X;
	this.Y = Y;
	this.length = Math.sqrt(X*X + Y*Y);
	
	//copy of vec2 scaled to unit length
	this.unit = function() {return new vec2(X/this.length, Y/this.length); };
	
	//inner product with vector V2
	this.inner = function(V2) { return X*V2.X + Y*V2.Y; };

	//normalized inner product between this and V2
	//= cosine of the angle between this vector and V2
	this.ninp = function(V2) {
		return (X*V2.X + Y*V2.Y) / (this.length * V2.length);
	};
	
	//copy of vec2 rotated by angle theta around origin
	this.rotate = function(theta) { 
		var c = Math.cos(theta);
		var s = Math.sin(theta);
		return new vec2(c*X-s*Y, s*X+c*Y);
	};
	
	//copy of vec2 translated by vector V2
	this.translate = function(V2) { return new vec2(X+V2.X, Y+V2.Y); };
	
	//copy of vec2 scaled with factor f
	this.scale = function(f) { return new vec2(f*X, f*Y); };

    //the distance between a point represented by this and a point represented by V2 (new in v6)
    this.dist = function (V2) { 
        var dX = V2.X - this.X;
        var dY = V2.Y - this.Y;
        return Math.sqrt(dX*dX + dY*dY);
    };
	
	//for debugging
	this.toString = function() { return "(" + X + "," + Y + ")"; };
};

//vereenvoudigt polygoon (array van vec2) naar een trapezium met 4 'ware' hoekpunten
function trap_simplify(poly){
    var corners = new Array();
	var np = poly.length - 1;
	var p;
	
    for (p = 0; p <= np - 1; p++) {
		var v1 = poly[p%np];
		var v2 = poly[(p+1)%np];
		var v3 = poly[(p+2)%np];
		var dv1 = new vec2(v2.X - v1.X, v2.Y - v1.Y);
		var dv2 = new vec2(v3.X - v2.X, v3.Y - v2.Y);
		var cosalfa = dv1.ninp(dv2);
		if (cosalfa<0.87) corners.push(v2);
	}
	
	corners.push(corners[0]); //het laatste punt is het eerste punt
	return corners;
};

//----- extraheert projectieparameters uit poly (array van vec2) 
//overige benodigde parameters: ImW, ImH, beta, H, method (zie proj_params voor omschrijving method-parameter)
function extract_params(poly_complex, ImW, ImH, beta, H, method) {
	var poly = trap_simplify(poly_complex);
	var N = poly.length;
	var i;
	
	//dV = lijst met verschilvectoren tussen hoekpunten
	var dV = new Array();
	for (i=0; i<N-1; i++) {
		dV.push(poly[i+1].translate(poly[i].scale(-1.0)));
	};
	
	//M = lijst met middelpunten tussen hoekpunten
	var M = new Array();
	for (i=0; i<N-1; i++) {
		M.push(poly[i].translate(poly[i+1]).scale(0.5));
	};
	
	function checkpair(i) {//
		var eps = 0.001; //tolerantie in parallelliteitstest
		var p = dV[0].unit().inner(dV[2].unit());
		if ((-1.0-eps)<=p && (-1.0+eps)>=p) { return true; } 
			else { return false; };
	};

	//als checkpair 0 = true, dan is T1 gegeven door de 1e of 3e zijde. Zo niet, dan door de 2e of 4e zijde.
	var V1;
	var V2;
	var Torig;
	var tau;
	if (checkpair(0)) {
		if (dV[0].length < dV[2].length) {
			V1 = dV[0];
			V2 = dV[2];
			Torig = M[0];
			tau = Math.atan2((M[2].X - Torig.X), (M[2].Y - Torig.Y));
		} else {
			V1 = dV[2];
			V2 = dV[0];
			Torig = M[2];
			tau = Math.atan2((M[0].X - Torig.X), (M[0].Y - Torig.Y));
		};
	} else {
		if (dV[1].length < dV[3].length) {
			V1 = dV[1];
			V2 = dV[3];
			Torig = M[1];
			tau = Math.atan2((M[3].X - Torig.X), (M[3].Y - Torig.Y));
		} else {
			V1 = dV[3];
			V2 = dV[1];
			Torig = M[3];
			tau = Math.atan2((M[1].X - Torig.X), (M[1].Y - Torig.Y));
		};
	};
	
	//bereken de parameters
	var T1 = V1.length;
	var T2 = V2.length;
	
	return new proj_params(poly, T1, T2, Torig, tau, ImW, ImH, beta, H, method);
};

//---- een transformatorobject dat vectoren tussen stelsels projecteert. De oude transformator(V,P) staat nog in versies 5 en eerder.
//gegeven vector V (type vec2) en parameterset P (van type proj_params) 
//elke transformatie V -> V' neemt een vector en geeft een nieuwe getransformeerde vector terug.
//TODO: v5->v6 routines trap_to_map en map_to_trap modificeren. Bewaar oude map_to_trap (map_to_trap_old) voor functies warp/unwarp
function transformator(P) {
	this.P = P;
	
	//hulpparameters
	var A = P.H * Math.tan(P.beta - P.alfav);
	var B = P.H * Math.tan(P.beta + P.alfav);
	
	//converteert V=(xt,yt) van trapeziumstelsel naar hat-stelsel (xhat,yhat)
	this.trap_to_hat = function (V) {
		var alfay = Math.atan((V.Y + A) / P.H) - P.beta;
		var yhat = Math.tan(alfay) / Math.tan(P.alfav);
		var yp = V.Y / (B-A);
		var xhat = 2.0*V.X / ((1.0-yp)*P.T1 + yp*P.T2);
		return new vec2(xhat,yhat);
	};

	//converteert V = (xhat,yhat)  van hat-stelsel naar trapeziumstelsel (xt,yt)
	this.hat_to_trap = function (V) {
		var tb = Math.tan(P.beta);
		var tay = V.Y * Math.tan(P.alfav);
		var yt = P.H * ((tb+tay) / (1.0 - tb*tay)) - A;
		var yp = yt / (B-A);
		var xt = 0.5 * V.X * ((1.0-yp)*P.T1 + yp*P.T2);
		return new vec2(xt,yt);
	};
	
	//OBSOLETE converteert V = (x,y) van kaartstelsel naar trapeziumstelsel (xt,yt)
	this.map_to_trap_old = function(V) {
		var T = new vec2(-P.Torig.X, -P.Torig.Y);
		return V.translate(T).rotate(P.tau);		
	};
	
	//OBSOLETE converteert V = (xt,yt) van trapeziumstelsel naar kaartstelsel (x,y) 
	this.trap_to_map_old = function(V) {
		var T = new vec2(P.Torig.X, P.Torig.Y);
		return V.rotate(-P.tau).translate(T);
	};
	
    //nieuw in v6 (unwarp): converteert V = (x,y) van kaartstelsel naar trapeziumstelsel (xt,yt)
	this.map_to_trap = function(V) {
		var T = new vec2(-P.Torig.X, -P.Torig.Y);
		var Vpre = V.translate(T).rotate(P.tau);		
        return Vpre.translate(P.getDx(Vpre).scale(-1.0)); //unwarp
	};
	
	//nieuw in v6 (warp): converteert V = (xt,yt) van trapeziumstelsel naar kaartstelsel (x,y) 
	this.trap_to_map = function(V) {
        var Vw = V.translate(P.getDx(V));                 //warp
		var T = new vec2(P.Torig.X, P.Torig.Y);
		return Vw.rotate(-P.tau).translate(T);
	};
    
	//converteert V = (p,q) van fotostelsel naar hat-stelsel (xhat,yhat)
	this.scr_to_hat = function(V) {
		var xhat = 2.0 * (V.X+0.5 - P.ImW/2) / P.ImW;
		var yhat = 2.0 * (V.Y+0.5 - P.ImH/2) / P.ImH;
		return new vec2(xhat,yhat);
	};
	
	//converteert V = (xhat,yhat) van hat-stelsel naar fotostelsel (p,q)
	this.hat_to_scr = function(V) {
		var p = 0.5 * (P.ImW-1.0) * (V.X + 1.0);
		var q = 0.5 * (P.ImH-1.0) * (V.Y + 1.0);
		return new vec2(p,q);
	};
	
	//------------ Om onderstaande functies draait het: transformeren tussen fotopixel- en mapstelsels --------------
	//converteert V = (x,y) van kaartstelsel naar fotopixels (p,q)
	this.map_to_scr = function(V) {
		//return this.map_to_trap(V).trap_to_hat().hat_to_scr().V;
		return this.hat_to_scr(this.trap_to_hat(this.map_to_trap(V)));
	};
	
	this.scr_to_map = function(V) {
		//return this.scr_to_hat().hat_to_trap().trap_to_map().V;
		return this.trap_to_map(this.hat_to_trap(this.scr_to_hat(V)));
	};
	
    //BETA: hoogteschatter
    //Input: twee vec2's met fotocoördinaten: V1 op de grond, en V2 op hoogte
    this.estimateHeight = function (V1,V2) {
        //excl. warp(!)
        var F1 = this.scr_to_hat(V1); 
        var F2 = this.scr_to_hat(V2);
        F1 = this.hat_to_trap(F1);
        F2 = this.hat_to_trap(F2);
        
        //warp
        F1 = F1.translate(P.getDx(V1)); //incl. warp(!)
        F2 = F2.translate(P.getDx(V2)); //incl. warp(!)
        
        var YHi = F2.Y;
        var YLo = F1.Y;
        var A = P.H * Math.tan(P.beta - P.alfav);
        
        return P.H * (YHi - YLo) / (A + YHi);
    };
    
	//for debugging
	this.toString = function() { return "Tr. Params:\n" + this.P; };
};


//------------------------------------------------------------
//---------------- GeoJSON-transformaties --------------------
//------------------------------------------------------------

//Transformeert recursief alle coördinatenparen binnen een coordinates object.
//Tr is een transformator2-object.
function transformcoords(coords,Tr,method) { 
	var i=0; //verplicht!, anders wordt i globaal. Onwenselijk!
	
	if (typeof coords[0] == "number") { //1e element sub-array is zelf geen array -> transformeer.
		var V = new vec2(coords[0],coords[1]);
	
		if (method==0) {
			var Vt = Tr.map_to_scr(V);
		} else {
			var Vt = Tr.scr_to_map(V);
		}

		coords[0] = Vt.X;
		coords[1] = Vt.Y;

	} else { //1e element sub-array is weer een array -> recursie.
		for (i = 0; i < coords.length; i++) {
			transformcoords(coords[i],Tr,method);
		}
	}
	return;
}

//transformeert een GJSON-object. !!!!!! NOOT!: doet niets met bbox-es. !!!!!!
//GJobj --> het geoJSON-object
//Tr --> het transformator2 object
//method = 0 --> map -> foto
//method = 1 --> foto -> map
//TODO: GJSON strings transformeren
function transformGJobj(GJobj,Tr,method) {
	for (key in GJobj) {
		var child = GJobj[key];
		
		if (typeof child != "string") {
			switch (key) {
				case "bbox":
					//TODO: transformeer bbox
					break;
				case "coordinates":
					transformcoords(child,Tr,method);
					break;
				default:
					if (child.length) { //child.length bestaat --> child is een array
						var k = 0;
						for (k=0; k<child.length; k++) {
							transformGJobj(child[k],Tr,method);
						}
					}
					else { //child = non-array
							transformGJobj(child,Tr,method);
					}
					break;
			}
		}
	}

	return;
}

//transformeert een GJSON-string. NOOT!: doet niets met bbox-es. 
//GJobj --> het geoJSON-object
//Tr --> een transformator2 object dat de transformatie beschrijft.
//method = 0 --> map -> foto
//method = 1 --> foto -> map
function transformGJstring(GJstr, Tr, method){
	var gjo = JSON.parse(GJstr); //Gebruik hier een parser naar keuze.
	transformGJobj(gjo, Tr, method);
	return JSON.stringify(gjo);
}

/*
//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE
var cam4 = new Array(
	new vec2(736701.384126494,6905025.02209323),
	new vec2(736563.079424844,6904585.64880027),
	new vec2(736009.355785083,6904837.38138266),
	new vec2(736229.980635967,6905569.39356307),
	new vec2(736844.356179186,6905472.80652713),
	new vec2(736701.384126494,6905025.02209323)
);

var cam2 = new Array(
	new vec2(736579.237197152,6905179.45668349),
	new vec2(736375.371685858,6904497.83179291),
	new vec2(735786.529539953,6904581.19464657),
	new vec2(736041.737293125,6905434.64193404),
	new vec2(736579.237197152,6905179.45668349)
);


//parameters berekend uit bovenstaand polygoon (000777-121908130812-Cam3) en gegeven vlieghoogte 609 (meter?)
var poly = cam2;
var beta = 24.0;//31.0;

var PP = extract_params(poly, 5616, 3744, helper.rad(beta), 994.0, 0); //beta in radialen (!!) 
var Tr = new transformator2(PP);

var straten = 
'{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736152.453031194,6904500.67779905],[736158.019005734,6904500.67779905],[736162.471785366,6904497.0144239],[736172.490539537,6904502.50948725],[736175.830124261,6904518.99469985],[736174.716929353,6904528.15316591]]}, "properties":{"id": "604","name": "De Achterbrink"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736480.845529035,6905145.45785339],[736485.298308666,6905143.62601855],[736536.505274431,6905167.43990406],[736543.184443879,6905178.43095197],[736545.410833695,6905193.0857059],[736550.976808234,6905205.90863754]]}, "properties":{"id": "608","name": "Vechtvoorde"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736653.390739764,6904727.8103216],[736619.994892526,6904724.14684285],[736576.580291117,6904725.97858202],[736545.410833695,6904733.30554285],[736538.731664247,6904738.80076787],[736537.618469339,6904801.08024751]]}, "properties":{"id": "613","name": "Hoge Doelen"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736651.164349948,6904786.42620886],[736646.711570316,6904788.25796223],[736558.76917259,6904786.42620886],[736555.429587866,6904802.91200423]]}, "properties":{"id": "614","name": "Wilhelminaplein"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736152.453031194,6904500.67779905],[736151.339836286,6904588.59930387]]}, "properties":{"id": "646","name": "De Achterbrink"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736680.107417554,6905149.12152432],[736672.315053199,6905189.42201492]]}, "properties":{"id": "647","name": "Badhuisplein"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736653.390739764,6904727.8103216],[736653.390739764,6904782.76270337],[736651.164349948,6904786.42620886]]}, "properties":{"id": "662","name": "Lage Doelen"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736525.373325352,6904799.24849122],[736537.618469339,6904801.08024751]]}, "properties":{"id": "663","name": "Wilhelminaplein"}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736494.20386793,6904923.80887096],[736469.713579955,6904976.93079699]]}, "properties":{"id": "670","name": ""}},{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736705.710900437,6904867.02375236],[736699.031730989,6904934.79958545]]}, "properties":{"id": "675","name": "Markt"}}]}'

var gjcam4 = 
'{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736701.384126494,6905025.02209323],[736563.079424844,6904585.64880027],[736009.355785083,6904837.38138266],[736229.980635967,6905569.39356307],[736844.356179186,6905472.80652713],[736701.384126494,6905025.02209323]]}, "properties":{"id": "604","name": "cam4"}}]}';

var gjcam2 = 
'{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "LineString", "coordinates": [[736579.237197152,6905179.45668349],[736375.371685858,6904497.83179291],[735786.529539953,6904581.19464657],[736041.737293125,6905434.64193404],[736579.237197152,6905179.45668349]]}, "properties":{"id": "602","name": "cam2"}}]}';

//alert(gjcam4);
//alert(transformGJstring(gjcam4,Tr,0));
alert(gjcam2);
alert(transformGJstring(gjcam2,Tr,0));
//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE//TESTGEDEELTE
*/
