# Source: js/models/filters/SpatialFilter.js

    define(["underscore", "jquery", "backbone", "models/filters/Filter"],
        function(_, $, Backbone, Filter) {

            /**
            *A SpatialFilter represents a spatial constraint on the query to be executed,
            *and stores the geohash strings for all of the geohash tiles that coincide with the
            * search bounding box at the given zoom level.
            * @class SpatialFilter
            * @name SpatialFilter
            * @constructs
            * @extends Filter
            */
            var SpatialFilter = Filter.extend(
              /** @lends SpatialFilter.prototype */{

                /* The type of the filter */
                type: "SpatialFilter",

                /**
                * @type {object}
                * @property {string} icon - The icon to display with the Spatial filter
                * @property {string} label - A short intuitive label
                */
                defaults: function() {
                    return _.extend(Filter.prototype.defaults(), {

                        /* The array of geohashes used to spatially constrain the search*/
                        geohashes: [],

                        /* The easternmost longitude of the represented bounding box */
                        east: null,

                        /* The westernmost longitude of the represented bounding box */
                        west: null,

                        /* The northernmost latitude of the represented bounding box */
                        north: null,

                        /* The southernmost latitude of the represented bounding box */
                        south: null,

                        /* The default precision level of the geohash-based search */
                        geohashLevel: null,

                        /* The list of grouped geohashes, as complete or incomplete 32 tiles */
                        groupedGeohashes: {},

                        label: "Limit search to the map area",
                        icon: "globe"
                    });
                },

                /* Standard events and callbacks for this model */
                events: {
                    "change geohashes": this.groupGeohashes
                },

                /**
                 * Initialize the model, calling super
                 */
                initialize: function(attributes, options) {
                    this.on("change:geohashes", this.groupGeohashes);
                },

                /**
                 * Builds a query string that represents this spatial filter
                 * @return queryFragment - the query string representing the geohash constraints
                 */
                getQuery: function() {
                    var queryFragment = "";
                    var geohashes = this.get("geohashes");
                    var groups = this.get("geohashGroups");
                    var geohashList;

                    // Only return geohash query fragments when they are enabled in the filter
                    if ( (typeof geohashes !== "undefined") && geohashes.length > 0 ) {
                        if ( (typeof groups !== "undefined") &&
                            Object.keys(groups).length > 0
                        ) {
                            // Group the Solr query fragment
                            queryFragment += "+(";

                            // Append geohashes at each level up to a fixed query string length
                            _.each(Object.keys(groups), function(level) {
                                geohashList = groups[level];
                                queryFragment += "geohash_" + level + ":(";
                                _.each(geohashList, function(geohash) {
                                    if ( queryFragment.length < 7900 ) {
                                        queryFragment += geohash + "%20OR%20";
                                    }
                                });
                                // Remove the last OR
                                queryFragment =
                                    queryFragment.substring(0, (queryFragment.length - 8));
                                queryFragment += ")%20OR%20";
                            });
                            // Remove the last OR
                            queryFragment = queryFragment.substring(0, (queryFragment.length - 8));
                            // Ungroup the Solr query fragment
                            queryFragment += ")";

                        }
                    }
                    return queryFragment;
                },

                /**
                 *  Consolidate geohashes into a parent tile if all are present
                 */
                groupGeohashes: function() {
                    var geohashGroups = {};
                    var sortedGeohashes = this.get("geohashes").sort();
                    var groupedGeohashes = _.groupBy(sortedGeohashes, function(geohash) {
                        return geohash.substring(0, geohash.length - 1);
                    });
                    //Find groups of geohashes that makeup a complete geohash tile (32)
                    // so we can shorten the query
                    var completeGroups = _.filter(Object.keys(groupedGeohashes), function(group) {
                        return groupedGeohashes[group].length == 32;
                    });

                    // Find groups that fall short of 32 tiles
                    var incompleteGroups = [];
                    _.each(
                        _.filter(Object.keys(groupedGeohashes), function(group) {
                            return (groupedGeohashes[group].length < 32)
                        }), function(incomplete) {
                            incompleteGroups.push(groupedGeohashes[incomplete]);
                        }
                    );
                    incompleteGroups = _.flatten(incompleteGroups);

                    // Add both complete and incomplete groups to the instance property
                    if((typeof incompleteGroups !== "undefined") && (incompleteGroups.length > 0)) {
                        geohashGroups[incompleteGroups[0].length.toString()] = incompleteGroups;
                    }
                    if((typeof completeGroups !== "undefined") && (completeGroups.length > 0)) {
                        geohashGroups[completeGroups[0].length.toString()] = completeGroups;
                    }
                    this.set("geohashGroups", geohashGroups); // Triggers a change event
                },
            });
            return SpatialFilter;
    });