UNPKG

terriajs

Version:

Geospatial data visualization platform.

185 lines (150 loc) 7.25 kB
'use strict'; /*global require*/ var inherit = require('../Core/inherit'); var SearchProviderViewModel = require('./SearchProviderViewModel'); var SearchResultViewModel = require('./SearchResultViewModel'); var zoomRectangleFromPoint = require('../Map/zoomRectangleFromPoint'); var defaultValue = require('terriajs-cesium/Source/Core/defaultValue'); var defined = require('terriajs-cesium/Source/Core/defined'); var loadJson = require('../Core/loadJson'); var GazetteerSearchProviderViewModel = function(options) { SearchProviderViewModel.call(this); options = defaultValue(options, defaultValue.EMPTY_OBJECT); this.terria = options.terria; this._geocodeInProgress = undefined; this.name = 'Official Place Names'; this.url = defaultValue(options.url, 'http://www.ga.gov.au/gazetteer-search/gazetteer2012/select/'); this.forceUseOfProxy = defaultValue(options.forceUseOfProxy, true); this.flightDurationSeconds = defaultValue(options.flightDurationSeconds, 1.5); }; inherit(SearchProviderViewModel, GazetteerSearchProviderViewModel); GazetteerSearchProviderViewModel.prototype.search = function(searchText) { if (!defined(searchText) || /^\s*$/.test(searchText)) { this.isSearching = false; this.searchResults.removeAll(); return; } this.isSearching = true; this.searchResults.removeAll(); this.searchMessage = undefined; this.terria.analytics.logEvent('search', 'gazetteer', searchText); // If there is already a search in progress, cancel it. if (defined(this._geocodeInProgress)) { this._geocodeInProgress.cancel = true; this._geocodeInProgress = undefined; } // I don't know how to get server-side sorting, so we have to retrieve lots of rows, then filter. // Retrieving only 10 rows (default) means "Sydney" fails to actually retrieve Sydney... // Worth considering using "fq=class_code:(R%20X)", which would only return towns, states etc var url = this.url + '?q=name:*' + searchText + '*'; // filter out bores, built structures, caves, landmarks, trig stations url += '%20-class_code:(D%20E%20G%20J%20T)%20-feature_code:PRS'; url += '&rows=200'; if (this.forceUseOfProxy || this.terria.corsProxy.shouldUseProxy(url)) { url = this.terria.corsProxy.getURL(url); } var promise = loadJson(url); var that = this; var geocodeInProgress = this._geocodeInProgress = promise.then(function(solrQueryResponse) { if (geocodeInProgress.cancel) { return; } that.isSearching = false; if (defined(solrQueryResponse.response) && solrQueryResponse.response.numFound > 0) { var results = solrQueryResponse.response.docs.sort( function (a,b) { return sortResults(a,b,searchText); } ); results = stripDuplicates(results); results.slice(0,10).forEach(function(result) { var locLat = result.location.split(',')[0]; var locLng = result.location.split(',')[1]; that.searchResults.push(new SearchResultViewModel({ name: result.name + (result.state_id !== 'N/A' ? ' (' + result.state_id + ')' : ''), isImportant: !!result.feature_code.match('^(CNTY|CONT|DI|PRSH|STAT|LOCB|LOCU|SUB|URBN)$'), clickAction: createZoomToFunction(that, locLat, locLng), location: { latitude: locLat, longitude: locLng } })); }); } else { that.searchMessage = 'Sorry, no official place names match your search query'; } that.isSearching = false; }).otherwise(function() { if (geocodeInProgress.cancel) { return; } that.isSearching = false; that.searchMessage = 'An error occurred while searching. Please check your internet connection or try again later.'; }); return geocodeInProgress; }; // Given a list of results sorted in decreasing importance, strip results that are close to another result with the same name function stripDuplicates(results) { var i; var placeshash={}; var stripped = []; for (i=0; i < results.length; i++) { var lat = Number(results[i].location.split(',')[0]).toFixed(1); var lng = Number(results[i].location.split(',')[1]).toFixed(1); var hash = results[i].name + '_' + lat + ' ' + lng; if (!defined(placeshash[hash])) { placeshash[hash] = results[i]; stripped.push(results[i]); } } return stripped; } function featureScore(a, searchText) { // could be further refined using http://www.ga.gov.au/image_cache/GA19367.pdf // feature_code is defined on p24 // class_code (A-X) matches a row in the table on p23 (eg, 'C' is 'Bays & Gulfs') var featureTypes = ['CONT', 'STAT', 'URBN', 'LOCB', 'LOCU', 'SUB', 'DI', 'CNTY', 'DI']; featureTypes.push('HBR', 'CAPE','PEN','PT', 'BAY','PORT','GULF', 'BGHT','COVE','MT','HILL','PEAK','OCEN','SEA','RESV','LAKE','RES','STRM'); featureTypes.push('PLN','REEF','VAL', 'PRSH'); var aScore = 10000 - (featureTypes.indexOf(a.feature_code) + 1) *100; if (aScore === 10000) { aScore = 0; } if (a.name.toUpperCase() === searchText.toUpperCase()) { // Bonus for exact match // More testing required to choose this value. Should "Geelong" (parish in Queensland) outrank "North Geelong" (suburb in Vic)? aScore += 10 * 100; } else if (a.name.match(new RegExp('^' + searchText + '\\b', 'i'))) { // bonus for first word match ('Steve Bay' better than 'West Steve Bay') aScore += 8 * 100; } else if (a.name.match(new RegExp('\\b' + searchText + '\\b', 'i'))) { // bonus for word-boundary match ('Steve' better than 'Steveville') aScore += 4 * 100; } else if (a.name.match(new RegExp('^' + searchText, 'i'))) { // bonus for word-boundary match ('Steventon' better than 'Nosteve Town') aScore += 2 * 100; } if (a.state_id === 'N/A') { // seems to be an indicator of a low quality result aScore -= 10 * 100; } if (a.status === 'U') { // Not official? H=historical, U=unofficial. Bleh. aScore -= 5 * 100; } if (a.status === 'H') { aScore -= 10 * 100; } return aScore; } function sortResults(a, b, searchText) { return featureScore(b, searchText) - featureScore(a, searchText); } function createZoomToFunction(viewModel, locLat, locLng) { // Server does not return information of a bounding box, just a location. // bboxSize is used to expand a point var bboxSize = 0.2; var rectangle = zoomRectangleFromPoint(locLat, locLng, bboxSize); return function() { var terria = viewModel.terria; terria.currentViewer.zoomTo(rectangle, viewModel.flightDurationSeconds); }; } module.exports = GazetteerSearchProviderViewModel;