Source: searchers/geoSearcher.js

 /** 
 *  
 * @class Searches gst geoservice (http://www.kortforsyningen.dk/dokumentation/geonoeglergeosearch)
 * @constructs Septima.Search.GeoSearch
 * @param {Object} options GeoSearch expects these properties:
 * @param options.area {string} "muncode" + municipalitycode eg.; "muncode0151"
 * @param options.targets {string[]} List of targets to search. The full list is ['adresser','stednavne', 'kommuner', 'matrikelnumre', 'opstillingskredse', 'politikredse', 'postdistrikter', 'regioner', 'retskredse']
 * @param options.authParams {Object} Either {ticket: 'ttttt'}, where t is a ticket issued by kortforsyningen or {login: 'lll', password: 'pppp'}
 *  
 * @example 
 * var geoSearchOptions = {
 *     onSelect : function(result) {
 *          jQuery('#log').prepend('<b>'+ result.data.type + ':</b> '+ result.title + '<br/>');
 *     },
 *     area: "muncode0151",
 *     targets : ['adresser','stednavne', 'kommuner'],
 *     authParams: {
 *      login: 'xxxx',
 *      password: 'xxxxxx'
 *     },
 *     matchesPhrase: "Begynder med ",
 *     searchDelay: 200
 * };
 * searchers.push({title : 'GeoSearch Feature', searcher : new Septima.Search.GeoSearch(geoSearchOptions)});
 *
 */
Septima.Search.GeoSearch = Septima.Class (Septima.Search.Searcher, /**  @lends Septima.Search.GeoSearch# */{

	serviceUrl: 'https://kortforsyningen.kms.dk',

    geoResources: [
                 {resource: 'adresser', type: '', title: 'Adresser'},
                 {resource: 'matrikelnumre', type: 'matrikelnummer', title: 'Matrikelnumre'},
                 {resource: 'kommuner', type: 'kommune', title: 'Kommuner'},
                 {resource: 'opstillingskredse', type: 'opstillingskreds', title: 'Opstillingskredse'},
                 {resource: 'politikredse', type: 'politikreds', title: 'Politikredse'},
                 {resource: 'postdistrikter', type: 'postdistrikt', title: 'Postdistrikter'},
                 {resource: 'regioner', type: 'region', title: 'Regioner'},
                 {resource: 'retskredse', type: 'retskreds', title: 'Retskredse'},
                 {resource: 'stednavne_v2', type: 'stednavn', title: 'Stednavne'}
    ],
    hasAddressInResources: null,
    
	/**
	 * This function is called when a result is selected.
	 * @callback Septima.Search.GeoSearch~selectCallback
	 * @param {Object} result
	 * @param result.title {string}
	 * @param result.description {string}
	 * @param result.data {object} record as returned from geoservice. Adress record shown. See http://www.kortforsyningen.dk/dokumentation/geonoeglergeosearch for other return types
	 * @param result.data.addressAccessId {string}
	 * @param result.data.addressSpecificCount int
	 * @param result.data.districtName {string}
	 * @param result.data.districtSubDivisionIdentifier {string}
	 * @param result.data.geometryWkt {string}
	 * @param result.data.mailDeliverySublocationIdentifier {string}
	 * @param result.data.municipalityCode {string}
	 * @param result.data.municipalityName {string}
	 * @param result.data.postCodeIdentifier {string}
	 * @param result.data.presentationString {string}
	 * @param result.data.streetBuildingIdentifier {string}
	 * @param result.data.streetCode {string}
	 * @param result.data.streetName {string}
	 * @param result.data.type {string}
	 * @param result.data.validCoordinates {boolean}
	 * @param result.data.x: int
	 * @param result.data.y: int
	 */
    
	initialize: function (options) {
		options.usesGeoFunctions = true;
		this.Searcher(options);
		this.area = null;
		if (options.area){
			this.area = options.area; 
		}
		this.authParams = options.authParams;
		this.myResources = [];
		this.resourcesParam = '';
		if (!options.targets){
			options.targets = [];
		}
		this.hasAddressInResources = false;
		var resources = [];
		for (var i = 0; i < this.geoResources.length;i++){
			var resource = this.geoResources[i];
			for (var j = 0; j < options.targets.length;j++){
				if (resource.resource === options.targets[j] || options.targets[j] === '*'){
					this.myResources.push(resource);
					resources.push(resource.resource);
					this.registerTarget(resource.title);
					if (resource.resource =='adresser'){
						this.hasAddressInResources = true;
					}
				}
			}
		}
		this.resourcesParam = resources.join(',');
		
		this.returnTrueGeometry = true;
		if (options.returnTrueGeometry){
			this.returnTrueGeometry = options.returnTrueGeometry; 
		}
		this.returnCentroid = false;
		if (options.returnCentroid){
			this.returnCentroid = options.returnCentroid; 
		}
		this.returnEsrForMatrikelNummer = true;
		if (options.returnEsrForMatrikelNummer){
			this.returnEsrForMatrikelNummer = options.returnEsrForMatrikelNummer; 
		}
		this.iconURI = Septima.Search.icons.searchers.geoSearch.result;
	    this.roadIcon = Septima.Search.icons.searchers.geoSearch.road;
	    this.addressIcon = Septima.Search.icons.searchers.geoSearch.addressResult;
	},
	
	fetchData: function (query, caller){
		var callParams = this.createCallParams(query);
		
		if (query.queryString === ""){
			if (query.hasTarget || query.type === "list.force"){
				callParams.search = "a b c d e f g h i j k l m 1 2 3 4 5 6";
			}else{
				var create = this.createQueryResultFromResources(query);
				create.done(Septima.bind(function(caller, queryResult){
	    			caller.fetchSuccess(queryResult);
				}, this, caller));
				create.fail(Septima.bind(function(caller, error){
					if (caller.isActive() && error !== 'abort'){
						caller.fetchError(this, error);
					}
				}, this, caller));
				return;
			}
		}
		
		var call = this.callGeoSearch(callParams);
		call.done(Septima.bind(function(caller, query, data){
            data.query = query;
            if (caller.isActive()) {
    			caller.fetchSuccess(this.parseResult(data.data, query));
            }
		}, this, caller, query));
		call.fail(Septima.bind(function(caller, error){
			if (caller.isActive() && error !== 'abort'){
				caller.fetchError(this, error);
			}
		}, this, caller));
		
	},
	
	createCallParams: function(query){
		var params = {};
		var target = this.getTargetFromQuery(query);
		if (target === null){
			params.limit = (query.limit + 1) * this.myResources.length;
			if (params.limit > 100){
				params.limit = 100;
			}
			params.resources = this.resourcesParam;
		}else{
			params.limit = query.limit;
			params.resources = target.resource;
		}
		if (this.area){
			params.area = this.area;
		}
		params.search = query.queryString;
		return params;
	},
	
	createQueryResultFromResources: function(query){
		var deferred = jQuery.Deferred();
		var queryResult = this.createQueryResult();
    	for (i=0;i<this.myResources.length;i++){
    		var resource = this.myResources[i];
    		if (resource.resource !== 'adresser'){
        		var result = queryResult.addNewQuery(resource.title , null, resource.title + ":", null, null);
    		}
    	}

		if (this.hasAddressInResources){
			var params = {};
			params.limit = (query.limit + 1);
			if (params.limit > 100){
				params.limit = 100;
			}
			params.resources = 'adresser';
			if (this.area){
				params.area = this.area;
			}
			params.search = 'a';
			var call = this.callGeoSearch(params);
			
			call.done(Septima.bind(function(deferred, query, queryResult, data){
				var finalResult = this.parseResult(data.data, query);
				finalResult.addResults(queryResult.getAllResults()); 
				deferred.resolve(finalResult);
			}, this, deferred, query, queryResult));
			call.fail(Septima.bind(function(deferred, error){
				deferred.reject(error);
			}, this, deferred));
			
		}else{
			deferred.resolve(queryResult);
		}
		
		return deferred;
	},
	
	callGeoSearch: function(params){
		var deferred = jQuery.Deferred();
		var xhr = jQuery.ajax({
	        url: this.serviceUrl + '/Geosearch?service=GEO',
	        data: Septima.extend (params, this.authParams),
	        dataType: 'jsonp',
	        cache : false,
	        timeout : 10000,
	        crossDomain : true
        });
		xhr.done(Septima.bind(function(deferred, data, textStatus,  jqXHR){
    		if (jqXHR.status === 200){
    			deferred.resolve(data);
    		}else{
    			deferred.reject(jqXHR.statusText);
    		}
			
		}, this, deferred));
		
		xhr.fail(Septima.bind(function(deferred, jqXHR, textStatus, errorThrown){
        	if (textStatus.toLowerCase() === 'abort' ){
    			deferred.reject('abort');
        	}else{
    			deferred.reject(errorThrown);
        	}
		}, this, deferred));
		return deferred;
	},
	
	getTargetFromQuery: function(query){
		if (query.hasTarget && this.hasTarget(query.target)){
			for (var i=0;i<this.myResources.length;i++){
				var testTarget = this.myResources[i];
				if (query.target === testTarget.resource || query.target === testTarget.title){
					return testTarget;
				}
			}
		}
		return null;
	},
	
	parseResult: function (data, query){
		var queryResult = this.createQueryResult();
		if (data !== null){
			this.prettifyHits(data);
			var nextTypeIndex = 0;
			while (nextTypeIndex > -1 && nextTypeIndex < data.length){
				nextTypeIndex = this.procesTypeFromIndex(data, nextTypeIndex, query, queryResult);
			}
		}
		return queryResult;
	},
	
	prettifyHits: function(hits){
		for (var i=0;i<hits.length;i++){
			var hit = hits[i];
			this.setHitType(hit);
			this.setPresentation(hit);
		}
	},
	
	procesTypeFromIndex: function(data, fromIndex, query, queryResult){
		
		if (fromIndex < 0 || fromIndex >= data.length){
			return -1;
		}
		
		var index = fromIndex;
		var myType = data[index].type;
		var typeHits = [];
		
		while (index < data.length && data[index].type === myType){
			typeHits.push(data[index]);
			index++;
		}
		var newQueryString;
		var resultGeometry;
		var newQuery;
		var result;
		var description;
		var thisHit;
		if (myType === 'streetNameType' || myType === 'addressAccessType'){
			//Show some adresses as appetizer even if query.type is list and hits exceed query.limit
			var countOfAdressesToShow;
			if (query.type === "list.force"){
				countOfAdressesToShow = Math.min(query.limit, typeHits.length); 
			}else{
				countOfAdressesToShow = Math.min(query.limit, typeHits.length, 3);
			}
			for (var i = 0; i < countOfAdressesToShow; i++){
				thisHit = typeHits[i];
		        if (myType === 'streetNameType'){
		        	var newQueryAdress = thisHit.streetName + " <select>1</select>"  + ', ' + thisHit.postCodeIdentifier + ' ' + thisHit.districtName;
					newQueryString = "Adresser:" + newQueryAdress;
	                resultGeometry = this.translateWktToGeoJsonObject(thisHit.geometryWkt);
		        	newQuery = queryResult.addNewQuery(thisHit.presentationString, null, newQueryString, resultGeometry, thisHit);
		        	newQuery.image = this.roadIcon;
		        }else{
	                resultGeometry = this.translateWktToGeoJsonObject(thisHit.geometryWkt);
		        	result = queryResult.addResult(thisHit.presentationString, null, resultGeometry, thisHit);
		        	result.target = "Adresser";
		        	result.image = this.addressIcon;
		        }
			}
			if (typeHits.length >  countOfAdressesToShow && !query.hasTarget && query.type === 'list'){
				description = null;
				if (query.queryString.length > 0){
					if (myType === 'streetNameType'){
						description = "Flere veje matcher <em>" + query.queryString + "</em>";
					}else{
						description = "Flere Adresser matcher <em>" + query.queryString + "</em>";
					}
				}
				newQueryString = "Adresser:" + query.queryString;
	        	result = queryResult.addNewQuery("Flere Adresser", description, newQueryString, null, null);
	        	result.image = this.addressIcon;
			}
		}else{
			var target = this.getTargetFromQuery(query);
			var title = this.getTitleFromType(myType);
			if (target === null  && query.type !== "list.force"){
				if (typeHits.length === 1){
					thisHit = typeHits[0];
	                resultGeometry = this.translateWktToGeoJsonObject(thisHit.geometryWkt);
	                result = queryResult.addResult(thisHit.presentationString + " (" + title + ")", thisHit.descriptionString ? thisHit.descriptionString : null, resultGeometry, thisHit);
		        	result.target = title;
		        	if ((thisHit.type === 'matrikelnummer' && this.returnTrueGeometry) || this.returnCentroid){
		        		result.isComplete = false;
		        	}
				}else{
					description = null;
					newQueryString = '';
					if (this.myResources.length === 1){
						newQueryString = query.queryString;
					}else{
						newQueryString = title + ":" + query.queryString;
					}
					queryResult.addNewQuery(title, description, newQueryString, null, null);
				}
			}else{
				for (var k = 0; k < query.limit && k < typeHits.length; k++){
					thisHit = typeHits[k];
	                resultGeometry = this.translateWktToGeoJsonObject(thisHit.geometryWkt);
	                result = queryResult.addResult(thisHit.presentationString, thisHit.descriptionString ? thisHit.descriptionString : null, resultGeometry, thisHit);
		        	result.target = title;
		        	if ((thisHit.type === 'matrikelnummer' && this.returnTrueGeometry) || this.returnCentroid){
		        		result.isComplete = false;
		        	}
				}
			}
		}
		return index;
	},
	
	getTitleFromType: function(type){
		var title = '';
		for (var i = 0;i < this.geoResources.length;i++){
			if (type === this.geoResources[i].type){
				return this.geoResources[i].title;
			}
		}
		return title;
	},

	completeResult:function(result){
		var completeDeferred = jQuery.Deferred();
		if (result.isComplete){
			completeDeferred.resolve(result);			
		}else{
			result.isComplete = true;
			var params;
			var xhr;

	    	if (result.data.type === "matrikelnummer"){
	    		var trueGeometryDeferred = jQuery.Deferred();
	    		var esrNummerDeferred = jQuery.Deferred();
	    		
	    		if (this.returnTrueGeometry){
	        		params = {ejkode: result.data.elavskode, matnr: result.data.matrnr};
	        		params = Septima.extend (params, this.authParams);
	        		xhr = jQuery.ajax({
	        	        url: this.serviceUrl + '?servicename=RestGeokeys_v2&method=matrikelnr&f=jsonp&geometry=true',
	        	        data: params,
	        	        dataType: 'jsonp',
	        	        cache : false,
	        	        timeout : 10000,
	        	        crossDomain : true,
	        	        success : Septima.bind(function (trueGeometryDeferred, result, data, textStatus,  jqXHR) {
	        	        	if (data.features.length > 0 && data.features[0].geometry){
	            	        	result.geometry = data.features[0].geometry;
	        	        	}
	        	        	trueGeometryDeferred.resolve();
	        	        }, this, trueGeometryDeferred, result),
	        	        
	        	        error : Septima.bind(function (trueGeometryDeferred, result, jqXHR, textStatus, errorThrown) {
	        	        	trueGeometryDeferred.resolve();
	        	        }, this, trueGeometryDeferred, result)
	                });
	    		}else{
	    			trueGeometryDeferred.resolve();
	    		}
	    		
	    		if (this.returnEsrForMatrikelNummer){
	        		params = {ejkode: result.data.elavskode, matnr: result.data.matrnr};
	        		params = Septima.extend (params, this.authParams);
	        		xhr = jQuery.ajax({
	        	        url: this.serviceUrl + '?servicename=RestGeokeys_v2&method=esrejendom&f=jsonp&geometry=true',
	        	        data: params,
	        	        dataType: 'jsonp',
	        	        cache : false,
	        	        timeout : 10000,
	        	        crossDomain : true,
	        	        success : Septima.bind(function (esrNummerDeferred, result, data, textStatus,  jqXHR) {
	        	        	if (data.features.length > 0){
	            	        	result.data.esrejdnr = data.features[0].properties.esrejdnr;
	        	        	}
	        	        	esrNummerDeferred.resolve();
	        	        }, this, esrNummerDeferred, result),
	        	        
	        	        error : function (trueGeometryDeferred, result, jqXHR, textStatus, errorThrown) {
	        	        	var a = 2;
	        	        }
	                });
	    		}else{
	    			esrNummerDeferred.resolve();
	    		}
	    		
	    		jQuery.when(trueGeometryDeferred, esrNummerDeferred).done(Septima.bind(function(completeDeferred, result){
	        		completeDeferred.resolve(result);
	    		}, this, completeDeferred, result));

	    	}else if (this.returnCentroid && (result.data.type === "kommune" || result.data.type === "opstillingskreds" || result.data.type === "politikreds" || result.data.type === "postdistrikt" || result.data.type === "region" || result.data.type === "retskreds" || result.data.type === "stednavn")){
	    		result.geometry = this.getCentroid(result.geometry);
	    		completeDeferred.resolve(result);
	    	}else{
	    		completeDeferred.resolve(result);
	    	}
		}
		
		return completeDeferred.promise();
	},
	
	setPresentation: function(hit){
		switch (hit.type) {
		case "stednavn":
			var originalPresentationString = hit.presentationString;
			var subTypeStart = originalPresentationString.lastIndexOf("(");
			if (subTypeStart > 0){
				hit.presentationString = originalPresentationString.substr(0, subTypeStart - 1);
				hit.descriptionString = originalPresentationString.substring(subTypeStart + 1, originalPresentationString.length - 1);
			}
			
			break;
		case "matrikelnummer":
			hit.presentationString = hit.matrnr + ", " + hit.elavsnavn;
			break;
		case "opstillingskreds":
			hit.descriptionString = hit.storkredsNavn + "s storkreds";
			break;
		case "postdistrikt":
			hit.presentationString = hit.id + " " + hit.name;
			break;
		}
		return hit;
	},
	
	setHitType: function(hit){
		switch (hit.type) {
		case "streetNameType":
		case "addressAccessType":
		case "matrikelnummer":
		case "kommune":
		case "opstillingskreds":
		case "politikreds":
		case "postdistrikt":
		case "region":
		case "retskreds":
			return hit;
		case "andentopografiflade":
		case "andentopografipunkt":
		case "bebyggelse":
		case "begravelsesplads":
		case "bygning":
		case "campingplads":
		case "faergerutelinje":
		case "faergerutepunkt":
		case "farvand":
		case "fortidsminde":
		case "friluftsbad":
		case "havnebassin":
		case "idraetsanlaeg":
		case "jernbane":
		case "landskabsform":
		case "lufthavn":
		case "naturareal":
		case "navigationsanlaeg":
		case "restriktionsareal":
		case "rute":
		case "sevaerdighed":
		case "soe":
		case "standsningssted":
		case "terraenkontur":
		case "ubearbejdetnavnflade":
		case "ubearbejdetnavnlinje":
		case "ubearbejdetnavnpunkt":
		case "urentfarvand":
		case "vandloeb":
			hit.type = "stednavn";
			return hit;
		default:
			return hit;
		}
	},
	
	onSelect: function(result){
        if (result.newquery){
        	if (result.data && result.data.type && result.data.type === 'streetNameType'){
        		this._onSelectCallback(result);
        	}
        }else{
    		this._onSelectCallback(result);
        }
	},

	CLASS_NAME: 'Septima.Search.GeoSearch'
});