Source: BKGWebMap/Protocol/Geokodierung.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/Protocol.js
 * @requires OpenLayers/Format/GeoJSON.js
 * @requires OpenLayers/Request.js
 * @requires BKGWebMap/Util.js
 * @requires BKGWebMap/Protocol.js
 */

/**
 * @classdesc Basisklasse für Interaktion mit Ortssuche des BKGs basierend auf der OpenSearch-Schnittstelle.<br/>
 * Das Geokodierungs-Protokoll kann für die Dienste gdz_ortssuche, gdz_geokodierung und gdz_geokodierung_bund verwendet
 * werden. Es werden zwei Suchanfragen unterstützt:
 * <ul>
 *  <li>suggest: Vorschlagssuche für Autovervollständigung</li>
 *  <li>geocode: Objektsuche inkl. Geometrien</li>
 * </ul>
 *
 * @constructor BKGWebMap.Protocol.Geokodierung
 * @param {object} options - Optionen für Protokoll
 * @param {string} options.url - URL des Suchdienstes
 * @param {string} options.srs - 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.Geokodierung = OpenLayers.Class({
    /**
     * URL des Suchdienstes
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @type string
     */
    url: null,

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

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

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

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

    /**
     * Parser für Suggestions.
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @type OpenLayers.Format
     */
    suggestionParser: new OpenLayers.Format.JSON(),

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

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

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

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

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

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

    /**
     * Führt eine Vorschlagssuche aus.
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @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 = OpenLayers.Protocol.Response.SUCCESS;
            response.suggestions = this.cache[term];
            if(options.callback) options.callback.call(options.scope, response);
            return response;
        }

        // URL für Suggest
        var url = this.url + '/suggest.json';

        // Parameter
        var params = {
            query : term,
            count : this.maxSuggestTerms
        };

        if(this.bounds) {
            params.bbox = this.bounds.toBBOX(null, false);
            params.srsName = this.srs;
        }

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

        return response;
    },

    /**
     * Führt die Ortssuche aus.
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @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 });

        // URL für Geocode
        var url = this.url + '/geosearch.json';

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

        if(this.bounds) {
            params.bbox = this.bounds.toBBOX(null, false);
        }

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

        return response;
    },

    /**
     * Allgemeine Antwortauswertung.
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @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.Geokodierung
     * @param {XMLHttpRequest} request
     * @param {BKGWebMap.Protocol.Response} response
     */
    parseGeocodeResponse: function(request, response) {
        if ( request.responseText.length == 0 ) return;
        response.features = this.featureParser.read(request.responseText, null);
        this.updateDataModel(response.features);
    },

    /**
     * Aktualisiert das Datenmodell. Hierbei wird sichergestellt, dass das bbox-Attribut eine Geometrie wird.
     *
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @param {Array<OpenLayers.Feature.Vector>} features - Die FeatureCollection
     */
    updateDataModel: function(features) {
        var featureParser = this.featureParser;
        BKGWebMap.Util.each(features, function(index, feature) {
            if(feature.attributes.bbox)
                feature.attributes.bbox = featureParser.parseGeometry(feature.attributes.bbox);
        });
    },

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

        var data = this.suggestionParser.read(request.responseText, null);
        if(!data) return;

        response.suggestions = BKGWebMap.Util.map(
            function( item ) { return { label: item.highlighted, value: item.suggestion }; },
            data
        );

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

    /**
     * Erzeugt eine Funktion, die gegebene Funktion mit den Argumenten anwendet.
     *
     * @memberOf BKGWebMap.Protocol.Geokodierung
     * @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.Geokodierung
     * @param {BKGWebMap.Protocol.Response} response - Das Protokoll-Response-Objekt
     */
    abort: function(response) {
        if (response && response.request) {
            response.request.abort();
        }
    },

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