Source: BKGWebMap/Protocol/Geoindex.js

/*
 * Copyright (c) 2013 Bundesamt by Kartographie und Geodäsie.
 * See license.txt in the BKG WebMap distribution or repository for the
 * full text of the license.
 *
 * Author: Dirk Thalheim
 */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 * @requires OpenLayers/Format/GeoJSON.js
 * @requires OpenLayers/Request.js
 * @requires OpenLayers/Protocol.js
 * @requires BKGWebMap/Util.js
 * @requires BKGWebMap/Protocol.js
 */

/**
 * @classdesc Basisklasse für Interaktion mit Ortssuche des BKG <br/>
 * Das Geoindex Protokoll ist verantwortlich für die Interaktion mit dem Suchdienst Geoindex. Es werden im Wesentlichen
 * zwei Suchanfragen unterstützt:
 * <ul>
 *  <li>suggest: Vorschlagssuche für Autovervollständigung</li>
 *  <li>geocode: Objektsuche inkl. Geometrien</li>
 * </ul>
 *
 * @constructor BKGWebMap.Protocol.Geoindex
 * @param {object} options - Optionen für Protokoll
 * @param {string} options.string - URL des Suchdienstes
 * @param {string} options.srsName - Georeferenzierung für Suchergebnisse
 * @param {OpenLayers.Bounds} options.bounds - Räumliche Einschränkung der Suche
 * @param {string} options.maxSuggestTerms - Maximale Anzahl der Ergebnisse bei Vorschlagssuche
 */
BKGWebMap.Protocol.Geoindex = OpenLayers.Class({

    /**
     * URL des Suchdienstes
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type string
     */
    url: null,

    /**
     * Projektion für Koordinaten.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type string
     */
    srs: null,

    /**
     * Räumliche Einschränkung für Suchanfragen.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type OpenLayers.Bounds
     */
    bounds: null,

    /**
     * Maximale Anzahl an Vorschlägen
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type int
     */
    maxSuggestTerms : 10,

    /**
     * Parser für Features.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type OpenLayers.Format
     */
    featureParser: new OpenLayers.Format.GeoJSON(),

    /**
     * Autocomplete cache.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type object
     */
    cache : {},

    /**
     * Callback für Feature-Updates. Akzeptiert als Parameter zwei Arrays: features und suggestions.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type function
     */
    updateCallback: null,

    /**
     * Zeigt an, ob das Protokoll autocomplete unterstützt.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @type boolean
     */
    canAutocomplete: true,

    initialize: function (options) {
        OpenLayers.Util.extend(this, options);
    },

    destroy: function() {
        if(this.featureParser) {
            this.featureParser.destroy();
            this.featureParser = null;
        }
    },

    getBBOX: function() {
        return this.bounds.toBBOX(null, false) + ',' + this.srs;
    },

    /**
     * Leert den Suggest-Cache
     * @memberOf BKGWebMap.Protocol.Geoindex
     */
    clearCache: function() {
        this.cache = {};
    },

    /**
     * Führt eine Vorschlagssuche aus.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {string} term - Suchterm
     * @param {object} options - Response-Handling-Optionen
     * @param {function} options.callback
     * @param {object} options.scope
     * @return {BKGWebMap.Protocol.Response}
     */
    suggest: function( term, options ) {
        var response = new BKGWebMap.Protocol.Response({ requestType: 'suggest', data: term });

    	// Falls im Cache vorhanden, dann diesen verwenden
        if ( term in this.cache ) {
            response.code = BKGWebMap.Protocol.Response.SUCCESS;
            response.suggestions = this.cache[term];
            if(options.callback) options.callback.call(options.scope, response);
            return response;
        }

        var params = {
            request: "GetSuggestion",
            query : term,
            maxResults : this.maxSuggestTerms,
            highlight : 1
        };

        if(this.bounds) {
            params.bbox = this.getBBOX();
        }

        // request
        response.request = OpenLayers.Request.GET({
            url: this.url,
            params: params,
            callback: this.createCallback(this.handleResponse, response, options)
        });

        return response;
    },

    /**
     * Führt die Ortssuche aus.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {string} term - Suchterm
     * @param {object} options - Response-Handling-Optionen
     * @param {function} options.callback
     * @param {object} options.scope
     * @return {BKGWebMap.Protocol.Response}
     */
    geocode: function( term, options ) {
        var response = new BKGWebMap.Protocol.Response({ requestType: 'geocode', data: term });

        // set the params
        var params = {
            request: "GetFeature",
            query : term,
            srsName : this.srs
        };

        if(this.bounds) {
            params.bbox = this.getBBOX();
        }

        response.request = OpenLayers.Request.GET({
            url: this.url,
            params: params,
            callback: this.createCallback(this.handleResponse, response, options)
        });

        return response;
    },

    /**
     * Allgemeine Antwortauswertung.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {BKGWebMap.Protocol.Response} response - Responseobjekt
     * @param {object} options - Response-Handling-Optionen
     */
    handleResponse: function(response, options) {
        var request = response.request;
        if(request.status >= 200 && request.status < 300) {
            // success
            switch (response.requestType) {
                case 'geocode':
                    this.parseGeocodeResponse(request, response);
                    break;
                case 'suggest':
                    this.parseSuggestResponse(request, response);
                    break;
            }
            response.code = (response.features || response.suggestions)
                ? OpenLayers.Protocol.Response.SUCCESS
                : OpenLayers.Protocol.Response.FAILURE;
        } else {
            // failure
            response.code = OpenLayers.Protocol.Response.FAILURE;
        }

        if(options.callback)
            options.callback.call(options.scope, response);
    },

    /**
     * Wertet die Antowrt einer Geocode-Anfrage aus.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {XMLHttpRequest} request
     * @param {BKGWebMap.Protocol.Response} response
     */
    parseGeocodeResponse: function(request, response) {
        if ( request.responseText.length == 0 ) {
            return;
        }

        var features = this.featureParser.read(request.responseText, null);
        if(!features) {
            return;
        }

        this.updateDataModel(features);

        response.suggestions = BKGWebMap.Util.grep(features, function(value){ return value.attributes.type == 'MeintenSie';});
        response.features = BKGWebMap.Util.grep(features, function(value){ return value.attributes.type != 'MeintenSie';});
    },

    /**
     * Wertet die Antowrt einer Suggest-Anfrage aus.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {XMLHttpRequest} request
     * @param {BKGWebMap.Protocol.Response} response
     */
    parseSuggestResponse: function(request, response) {
        if ( request.responseText.length == 0 ) {
            return;
        }

        var data = new OpenLayers.Format.JSON().read(request.responseText, null);
        if(!data || !data.suggestions) {
            return;
        }

        response.suggestions = BKGWebMap.Util.map( function( item ) {
            return { label: item, value: item.replace(/<\/?B>/g,"") };
        }, data.suggestions );

        this.cache[response.data] = response.suggestions;
    },

    /**
     * Aktualisiert das Datenmodell entsprechend den neuen Geokodierungsdiensten.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {Array<OpenLayers.Feature.Vector>} features - Die FeatureCollection
     */
    updateDataModel: function(features) {
        BKGWebMap.Util.each(features, function(index, feature) {
            if(feature.geometry == undefined)
                return;

            feature.attributes.bbox = feature.geometry;
            feature.geometry = feature.geometry.getCentroid();

            feature.attributes.text = feature.attributes.fulltext;
            feature.attributes.typ = feature.attributes.type;
        });
    },

    /**
     * Erzeugt eine Funktion, die gegebene Funktion mit den Argumenten anwendet.
     *
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {function} method - Zielfunktion
     * @param {BKGWebMap.Protocol.Response} response - Das Protokoll-Response-Objekt
     * @param {object} options - weitere Optionen für die Protokoll-Methode
     * @return {function} die neue Wrapper-Funktion
     */
    createCallback: function(method, response, options) {
        return OpenLayers.Function.bind(function() {
            method.apply(this, [response, options]);
        }, this);
    },

    /**
     * Bricht einen laufenden Request ab. Das Response-Objekt muss von diesem Protokoll stammen.
     * @memberOf BKGWebMap.Protocol.Geoindex
     * @param {BKGWebMap.Protocol.Response} response - Das Protokoll-Response-Objekt
     */
    abort: function(response) {
        if (response && response.request) {
            response.request.abort();
        }
    },

    CLASS_NAME: "BKGWebMap.Protocol.Geoindex"
});