terriajs
Version:
Geospatial data visualization platform.
243 lines (200 loc) • 8.46 kB
JavaScript
'use strict';
/*global require*/
var inherit = require('../Core/inherit');
var runLater = require('../Core/runLater');
var SearchProviderViewModel = require('./SearchProviderViewModel');
var SearchResultViewModel = require('./SearchResultViewModel');
var defaultValue = require('terriajs-cesium/Source/Core/defaultValue');
var defined = require('terriajs-cesium/Source/Core/defined');
var when = require('terriajs-cesium/Source/ThirdParty/when');
var CatalogItemNameSearchProviderViewModel = function(options) {
SearchProviderViewModel.call(this);
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this.terria = options.terria;
this._searchInProgress = undefined;
this.name = 'Catalogue Items';
this.maxResults = defaultValue(options.maxResults, 10000);
};
inherit(SearchProviderViewModel, CatalogItemNameSearchProviderViewModel);
CatalogItemNameSearchProviderViewModel.prototype.search = function(searchText) {
function parseSearchFilters() {
/* Filter search by type, eg 'is:wms fish' or '-is:wfs house'. Multiple 'is' clauses are or-ed. */
var isRE = /(^|\s)(-?)is:([a-zA-Z0-9_-]+)\b/i;
while (searchText.match(isRE)) {
if (!searchText.match(isRE)[2]) {
searchFilters.typeIs.push(searchText.match(isRE)[3].toLowerCase());
} else {
searchFilters.typeIsNot.push(searchText.match(isRE)[3].toLowerCase());
}
searchText = searchText.replace(isRE, '').trim();
}
/* Change number of search results: 'show:20' or 'show:all' */
var showRE = /\bshow:(all|[0-9]+)\b/i;
while (searchText.match(showRE)) {
searchFilters.maxResults = searchText.match(showRE)[1].toLowerCase();
if (searchFilters.maxResults === 'all') {
searchFilters.maxResults = 10000;
} else {
searchFilters.maxResults = parseInt(searchFilters.maxResults, 10);
}
searchText = searchText.replace(showRE, '').trim();
}
/* Filter by URL: 'url:landgate.wa' or '-url:geoserver.nicta.com.au' */
var urlRE = /(^|\s)(-?)url:([^ ]+)(\b|$)/i;
while (searchText.match(urlRE)) {
if (!searchText.match(urlRE)[2]) {
searchFilters.urlMatches.push(searchText.match(urlRE)[3].toLowerCase());
} else {
searchFilters.urlDoesNotMatch.push(searchText.match(urlRE)[3].toLowerCase());
}
searchText = searchText.replace(urlRE, '').trim();
}
}
if (this._searchInProgress) {
this._searchInProgress.cancel = true;
this._searchInProgress = undefined;
}
if (!defined(searchText) || /^\s*$/.test(searchText)) {
this.isSearching = false;
this.searchResults.removeAll();
return;
}
var searchFilters = { typeIs: [], typeIsNot: [], urlMatches: [], urlDoesNotMatch: [] };
parseSearchFilters();
this.isSearching = true;
this.searchResults.removeAll();
this.searchMessage = undefined;
this.terria.analytics.logEvent('search', 'catalogue', searchText);
var searchInProgress = this._searchInProgress = {
cancel: false
};
var path = [];
var topLevelGroup = this.terria.catalog.group;
var promise = findMatchingItemsRecursively(this, searchInProgress, new RegExp(searchText, 'i'), topLevelGroup, path, undefined, searchFilters);
var that = this;
return when(promise).then(function() {
that.isSearching = false;
if (searchInProgress.cancel) {
return;
}
if (that.searchResults.length === 0) {
that.searchMessage = 'Sorry, no catalogue items match your search query.';
}
});
};
function itemMatchesFilters(item, searchFilters) {
if (searchFilters.typeIs.length > 0 && searchFilters.typeIs.indexOf(item.type.toLowerCase()) < 0) {
return false;
}
if (searchFilters.typeIsNot.length > 0 && searchFilters.typeIsNot.indexOf(item.type.toLowerCase()) >= 0) {
return false;
}
if (!item.url) {
// if no URL, it can't match any positive filters, and can't fail by matching any negative ones.
return searchFilters.urlMatches.length === 0;
}
var r = true;
// multiple -url: filters are and-ed
searchFilters.urlDoesNotMatch.forEach(function(e) {
// we just do simple string matching, not regex
if (item.url.toLowerCase().indexOf(e) >= 0) {
r = false;
}
});
if (!r) {
return false;
}
if (searchFilters.urlMatches.length === 0) {
return true;
}
r = false;
// multiple url: filters are or-ed
searchFilters.urlMatches.forEach(function(e) {
// we just do simple string matching, not regex
if (item.url.toLowerCase().indexOf(e) >= 0) {
r = true;
}
});
return r;
}
function findMatchingItemsRecursively(viewModel, searchInProgress, searchExpression, group, path, promise, searchFilters) {
path.push(group);
if (!defined (searchFilters)) {
searchFilters = {};
}
var items = group.items;
var maxResults = (defined(searchFilters.maxResults) ? searchFilters.maxResults : viewModel.maxResults);
for (var i = 0; !searchInProgress.cancel && viewModel.searchResults.length < maxResults && i < items.length; ++i) {
var item = items[i];
if (item.isHidden) {
continue;
}
// Match non-top-level items whose name contain the search text.
if (searchExpression.test(item.name) && itemMatchesFilters(item, searchFilters)) {
addSearchResult(viewModel.searchResults, item, path);
}
if (defined(item.items)) {
promise = loadAndSearchGroup(viewModel, item, searchInProgress, searchExpression, path, promise, searchFilters);
}
}
path.pop();
return promise;
}
function loadAndSearchGroup(viewModel, group, searchInProgress, searchExpression, path, promise, searchFilters) {
path = path.slice();
// Let a previous load (represented by 'promise') finish first.
return when(promise).then(function() {
if (searchInProgress.cancel) {
return;
}
return runLater(function() {
if (searchInProgress.cancel) {
return;
}
var loadPromise = group.load();
if (defined(loadPromise) && group.isLoading) {
return loadPromise.then(function() {
return findMatchingItemsRecursively(viewModel, searchInProgress, searchExpression, group, path, undefined, searchFilters);
}).otherwise(ignoreLoadErrors);
} else {
return findMatchingItemsRecursively(viewModel, searchInProgress, searchExpression, group, path, undefined, searchFilters);
}
});
});
}
function ignoreLoadErrors() {
}
function addSearchResult(searchResults, item, path) {
// Get the index of an existing search result that refers to the same catalog item (or -1)
var index = -1;
for (var j = 0; j < searchResults.length; ++j) {
if (item === searchResults[j].catalogItem) {
index = j;
break;
}
}
// If a search result for item already exists, modify the tooltip of that search result
var prefix = 'In multiple locations including: ';
if (index >= 0) {
if (searchResults[index].tooltip.indexOf(prefix) !== 0) {
searchResults[index].tooltip = searchResults[index].tooltip.replace(/^In /, prefix);
}
}
else { // Otherwise, create a new search result
searchResults.push(new SearchResultViewModel({
name: item.name,
isImportant: true,
catalogItem: item,
tooltip: pathToTooltip(path)
}));
}
}
function pathToTooltip(path) {
var result = 'In Data Catalogue';
// Start at 1 to skip "Root Group"
for (var i = 1; i < path.length; ++i) {
result += ' -> ' + path[i].name;
}
return result;
}
module.exports = CatalogItemNameSearchProviderViewModel;