From 1785287cf689b51717a8077cf4aee01367e40032 Mon Sep 17 00:00:00 2001 From: Kevin Shrestha Date: Wed, 18 May 2016 10:41:03 +0200 Subject: [PATCH] #57 Added marker clustering. Implemented Prototype Async loading notification. Limited the frequency of http requests by implementing a queue system and preventing simultaneous requests. --- .../src/main/resources/static/bower.json | 3 +- .../src/main/resources/static/index.html | 14 ++ .../scripts/controllers/controller.list.js | 28 +++- .../scripts/controllers/controller.main.js | 61 +++++++- .../scripts/controllers/controller.map.js | 127 ++++++++++------ .../static/scripts/services/service.http.js | 140 ++++++++++++++---- .../main/resources/static/views/listView.html | 6 - .../main/resources/static/views/mapView.html | 12 +- 8 files changed, 295 insertions(+), 96 deletions(-) diff --git a/rest_service/src/main/resources/static/bower.json b/rest_service/src/main/resources/static/bower.json index 23137f3..e55a337 100644 --- a/rest_service/src/main/resources/static/bower.json +++ b/rest_service/src/main/resources/static/bower.json @@ -29,6 +29,7 @@ "ui-leaflet": "^1.0.0", "angular-ui-layout": "^1.4.2", "angular-bootstrap": "^1.3.2", - "angularjs-slider": "^2.13.0" + "angularjs-slider": "^2.13.0", + "leaflet.markercluster": "^0.5.0" } } diff --git a/rest_service/src/main/resources/static/index.html b/rest_service/src/main/resources/static/index.html index b6b7722..c50b5e9 100644 --- a/rest_service/src/main/resources/static/index.html +++ b/rest_service/src/main/resources/static/index.html @@ -16,6 +16,8 @@ + + @@ -23,6 +25,17 @@
+ {{alert.msg}} +
+ + + Status: {{timePassed}} s +
+ {{info}} + +
+
+
@@ -41,6 +54,7 @@ + diff --git a/rest_service/src/main/resources/static/scripts/controllers/controller.list.js b/rest_service/src/main/resources/static/scripts/controllers/controller.list.js index 709fb26..abe3c74 100644 --- a/rest_service/src/main/resources/static/scripts/controllers/controller.list.js +++ b/rest_service/src/main/resources/static/scripts/controllers/controller.list.js @@ -37,7 +37,6 @@ console.log(); $scope.dataSource = "accumulo"; - document.getElementById("loading").style.visibility = "hidden"; /** * Get the tweets array from the httpService @@ -49,23 +48,38 @@ httpService.setSearchToken($scope.search.inputValue); httpService.setSearchFields($scope.search.searchFields); - + /** + * get the tweets from the REST interface + */ if ($scope.dataSource == "accumulo") { - httpService.getTweetsFromServerByToken(); //Get by Token + //Get by GeoTime + httpService.getTweetsFromServerByToken().then(function (status) { + $scope.$emit('updateStatus', status); + }); } else if ($scope.dataSource == "restTest") { - httpService.getTweetsFromServerTest(); //Get using test REST API + //Get using test REST API + httpService.getTweetsFromServerTest().then(function (status) { + $scope.$emit('updateStatus', status); + }); } else if ($scope.dataSource == "static") { - httpService.getTweetsFromLocal(); //Get from local (debug) + //Get from local (debug) + httpService.getTweetsFromLocal().then(function (status) { + $scope.$emit('updateStatus', status); + }); } else { - httpService.getTweetsFromServerByToken(); //Get by Token + //Get by Token + httpService.getTweetsFromServerByToken().then(function (status) { + $scope.$emit('updateStatus', status); + }); } - // httpService.getTweetsFromServerByToken(); if (mode && mode === 'list') { } else if (mode && mode === 'map') { //TODO: Call Service to load Data for the Map view } + + $scope.$emit('updateStatus', "Loading: " + $scope.search.searchFields.text.checked + " | " + $scope.search.searchFields.user.checked + " | '" + $scope.search.inputValue + "'"); } } })(); \ No newline at end of file diff --git a/rest_service/src/main/resources/static/scripts/controllers/controller.main.js b/rest_service/src/main/resources/static/scripts/controllers/controller.main.js index 11c3e80..b11699e 100644 --- a/rest_service/src/main/resources/static/scripts/controllers/controller.main.js +++ b/rest_service/src/main/resources/static/scripts/controllers/controller.main.js @@ -20,8 +20,10 @@ * @type {string[]} */ MainCtrl.$inject = [ + '$rootScope', '$scope', - '$location' + '$interval', + 'httpService' ]; /** @@ -31,10 +33,61 @@ * @param $location * @constructor */ - function MainCtrl($scope,$location) { - + function MainCtrl($rootScope, $scope, $interval, httpService) { + $scope.app = []; $scope.app.name = "OSTMap"; - + + document.getElementById("loading").style.visibility = "hidden"; + + $scope.timePassed = 0; + $scope.info = ""; + var timestamp = 0; + + $scope.alerts = [ + // { type: 'danger', msg: 'Oh snap! Change a few things up and try submitting again.' }, + // { type: 'success', msg: 'Well done! You successfully read this important alert message.' } + ]; + + $scope.addAlert = function() { + $scope.alerts.push({msg: 'Another alert!'}); + }; + + $scope.closeAlert = function(index) { + $scope.alerts.splice(index, 1); + }; + + $scope.ignoreLoading = function () { + httpService.setLoading(false); + document.getElementById("loading").style.visibility = "hidden"; + } + + $scope.$on('updateStatus', function(event, message){ + if (message != 200) { + $scope.info = message; + } + $scope.setLoadingDisplay(httpService.getLoading(), message); + }); + + $scope.setLoadingDisplay = function (loadingStatus, message) { + if (loadingStatus) { + document.getElementById("loading").style.visibility = "visible"; + } else { + document.getElementById("loading").style.visibility = "hidden"; + } + + timestamp = Date.now() + var intervalPromise = $interval(function () { + $scope.timePassed = Math.round((Date.now() - timestamp)/100)/10; + + if(!httpService.getLoading()) { + $interval.cancel(intervalPromise); + } + }, 100); + }; + + $rootScope.$on('alertControl', function(event, message){ + // $scope.alerts.push({msg: 'Another alert!'}); + }); } })(); \ No newline at end of file diff --git a/rest_service/src/main/resources/static/scripts/controllers/controller.map.js b/rest_service/src/main/resources/static/scripts/controllers/controller.map.js index d7963ce..31e9eae 100644 --- a/rest_service/src/main/resources/static/scripts/controllers/controller.map.js +++ b/rest_service/src/main/resources/static/scripts/controllers/controller.map.js @@ -34,10 +34,8 @@ function MapCtrl($scope, httpService, $log, nemSimpleLogger, leafletData) { mapInit($scope); - document.getElementById("loading").style.visibility = "hidden"; - $scope.autoUpdateDisabled = true; - $scope.dataSource = "accumulo"; + $scope.dataSource = "accumulo"; //default: "accumulo"; $scope.currentFilters = ""; $scope.timeFilter = 0.25; @@ -77,45 +75,70 @@ /** * Update filters */ + var updateQueued = false; $scope.search.updateFilters = function () { - /** - * Pass the filters to the httpService - */ - httpService.setSearchToken($scope.search.searchFilter); - // httpService.setSearchToken("yolo"); - httpService.setTimeWindow(parseTimeFilter()); - httpService.setBoundingBox($scope.getBounds()); - /** - * get the tweets from the REST interface - */ - if ($scope.dataSource == "accumulo") { - httpService.getTweetsFromServerByGeoTime(); //Get by GeoTime - } else if ($scope.dataSource == "restTest") { - httpService.getTweetsFromServerTest(); //Get using test REST API - } else if ($scope.dataSource == "static") { - httpService.getTweetsFromLocal(); //Get from local (debug) + if (!httpService.getLoading()) { + /** + * Pass the filters to the httpService + */ + httpService.setSearchToken($scope.search.searchFilter); + httpService.setTimeWindow(parseTimeFilter()); + httpService.setBoundingBox($scope.getBounds()); + /** + * get the tweets from the REST interface + */ + httpService.queueAddGetTweetFrom($scope.dataSource, $scope.search); + + if ($scope.dataSource == "accumulo") { + //Get by GeoTime + httpService.getTweetsFromServerByGeoTime().then(function (status) { + $scope.$emit('updateStatus', status); + }); + } else if ($scope.dataSource == "restTest") { + //Get using test REST API + httpService.getTweetsFromServerTest().then(function (status) { + $scope.$emit('updateStatus', status); + }); + } else if ($scope.dataSource == "static") { + //Get from local (debug) + httpService.getTweetsFromLocal().then(function (status) { + $scope.$emit('updateStatus', status); + }); + } else { + //Get by Token + httpService.getTweetsFromServerByToken().then(function (status) { + $scope.$emit('updateStatus', status); + }); + } + + /** + * Update the filter display + * Check for null values, replace with Default + * + * @type {string} + */ + $scope.currentFilters = $scope.search.searchFilter + " | " + + $scope.search.hashtagFilter + " | " + + $scope.timeFilter + "h | " + + "[" + httpService.getBoundingBox().bbnorth.toFixed(2) + + ", " + httpService.getBoundingBox().bbwest.toFixed(2) + + ", " + httpService.getBoundingBox().bbsouth.toFixed(2) + + ", " + httpService.getBoundingBox().bbeast.toFixed(2) + "]"; + + console.log("Filters updated: " + $scope.currentFilters + " | " + $scope.bounds); + $scope.$emit('updateStatus', "Loading: " + $scope.currentFilters + " | " + $scope.bounds); } else { - httpService.getTweetsFromServerByToken(); //Get by Token + updateQueued = true; } - - /** - * Update the filter display - * Check for null values, replace with Default - * - * @type {string} - */ - $scope.currentFilters = $scope.search.searchFilter + " | " + - $scope.search.hashtagFilter + " | " + - $scope.timeFilter + "h | " + - "[" + httpService.getBoundingBox().bbnorth.toFixed(2) + - ", " + httpService.getBoundingBox().bbwest.toFixed(2) + - ", " + httpService.getBoundingBox().bbsouth.toFixed(2) + - ", " + httpService.getBoundingBox().bbeast.toFixed(2) + "]"; - - console.log("Filters updated: " + $scope.currentFilters + " | " + $scope.bounds); - }; + $scope.$on('updateStatus', function(event, message){ + if(updateQueued) { + $scope.search.updateFilters(); + updateQueued = false; + } + }); + /** * Move the map center to the coordinates of the clicked tweet * @@ -139,7 +162,7 @@ $scope.center ={ lat: lat, lng: lng, - zoom: 6 + zoom: 10 }; /** @@ -218,12 +241,13 @@ */ var newMarker = { id: tweet.id, + layer: 'cluster', lat: tweet.coordinates.coordinates[1], lng: tweet.coordinates.coordinates[0], focus: false, draggable: false, message: "@" + tweet.user.screen_name + ": " + tweet.text, - icon: $scope.icons.red + // icon: $scope.icons.red }; // $scope.markers.push(newMarker) // $scope.markers.push(tweet.id + ": " + newMarker) @@ -292,6 +316,8 @@ $scope.onBounds() }); + + /** * Update the filters when the bounds are changed */ @@ -300,9 +326,9 @@ map.on('moveend', function() { $scope.currentBounds = map.getBounds(); if($scope.autoUpdateDisabled) { - console.log("Map watcher triggered, autoUpdateDisabled: no action taken"); + // console.log("Map watcher triggered, autoUpdateDisabled: no action taken"); } else { - console.log("Map watcher triggered, updating filters"); + // console.log("Map watcher triggered, updating filters"); $scope.search.updateFilters(); } }); @@ -450,7 +476,7 @@ $scope.currentMarkerID = 0; /** - * Map event functions for future extensibility (Marker Clustering) + * Map functions for future extensibility (Marker Clustering) * https://asmaloney.com/2015/06/code/clustering-markers-on-leaflet-maps/ * http://leafletjs.com/2012/08/20/guest-post-markerclusterer-0-1-released.html * @@ -466,5 +492,22 @@ logic: 'emit' } }; + + $scope.layers = { + baselayers: { + osm: { + name: "OpenStreetMap", + type: "xyz", + url: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + } + }, + overlays: { + cluster: { + name: "Clustered Markers", + type: "markercluster", + visible: true + } + } + } } })(); \ No newline at end of file diff --git a/rest_service/src/main/resources/static/scripts/services/service.http.js b/rest_service/src/main/resources/static/scripts/services/service.http.js index d7631a7..d40359a 100644 --- a/rest_service/src/main/resources/static/scripts/services/service.http.js +++ b/rest_service/src/main/resources/static/scripts/services/service.http.js @@ -15,7 +15,9 @@ * @type {string[]} */ httpService.$inject = [ - '$http' + '$rootScope', + '$http', + '$q' ]; /** @@ -73,12 +75,28 @@ */ var _searchToken = ""; - function httpService($http) { + /** + * Loading status + * @type {boolean} + */ + var isLoading = false; + + var maxQueueSize = 2; + + var maxSimultaneous = 1; + + var queue = []; + + var processing = []; + + function httpService($rootScope, $http, $q) { return { getTweetsFromServerByToken: _getTweetsFromServerByToken, getTweetsFromServerByGeoTime: _getTweetsFromServerByGeoTime, getTweetsFromServerTest: _getTweetsFromServerTest, getTweetsFromLocal: _getTweetsFromLocal, + queueAddGetTweetFrom: _queueAddGetTweetFrom, + removeFromQueue: _removeFromQueue, getTweets: _getTweets, getSearchToken: _getSearchToken, setSearchToken: _setSearchToken, @@ -87,55 +105,99 @@ getBoundingBox: _getBoundingBox, setBoundingBox: _setBoundingBox, getTimeWindow: _getTimeWindow, - setTimeWindow: _setTimeWindow + setTimeWindow: _setTimeWindow, + getLoading: _getLoading, + setLoading: _setLoading, }; + function _queueAddGetTweetFrom(api, filters) { + if (queue.size < maxQueueSize) { + + } + + switch(api) { + case "accumulo": + $rootScope.$emit('alertControl', api); + break; + case "restTest": + $rootScope.$emit('alertControl', api); + break; + case "static": + $rootScope.$emit('alertControl', api); + break; + default: + $rootScope.$emit('alertControl', api); + } + + processQueue(); + } + + function _removeFromQueue(index) { + queue.splice(index, 1); + } + + function processQueue() { + if (processing.size < maxSimultaneous) { + processQueue(); + } else { + + } + } + function _getTweetsFromServerByToken() { - document.getElementById("loading").style.visibility = "visible"; + _setLoading(true); + var deferred = $q.defer(); var url = getTokenSearchUrl(); $http.get(url).success(function (data, status, headers, config) { //Copy result data to the private array angular.copy(data,_tweets); - document.getElementById("loading").style.visibility = "hidden"; + _setLoading(status); + deferred.resolve(status); }).error(function (data, status, headers, config) { //TODO: Log the errors - document.getElementById("loading").style.visibility = "hidden"; + _setLoading(status); + deferred.resolve(status + "\n" + headers + "\n" + config); }); + + return deferred.promise; } function _getTweetsFromServerByGeoTime() { - document.getElementById("loading").style.visibility = "visible"; + _setLoading(true); + var deferred = $q.defer(); var url = getGeoTemporalSearchUrl(); $http.get(url).success(function (data, status, headers, config) { //Copy result data to the private array angular.copy(data,_tweets); - console.log("HTTP response received") - document.getElementById("loading").style.visibility = "hidden"; + _setLoading(status); + deferred.resolve(status); }).error(function (data, status, headers, config) { //TODO: Log the errors - document.getElementById("loading").style.visibility = "hidden"; + _setLoading(status); + deferred.resolve(status + "\n" + headers + "\n" + config); }); - }; + + return deferred.promise; + } function _getTweetsFromServerTest() { - document.getElementById("loading").style.visibility = "visible"; + _setLoading(true); + var deferred = $q.defer(); - var url = "http://localhost:8080/api/testgeo?bbnorth=" + _boundingBox.bbnorth - + "&bbsouth=" + _boundingBox.bbsouth - + "&bbeast=" + _boundingBox.bbeast - + "&bbwest=" + _boundingBox.bbwest - + "&tstart=" + _timePeriod.tstart - + "&tend=" + _timePeriod.tend; + var url = "http://localhost:8080/api/testgeo?xyz"; $http.get(url).success(function (data, status, headers, config) { //Copy result data to the private array angular.copy(data,_tweets); - console.log("HTTP response received") - document.getElementById("loading").style.visibility = "hidden"; + _setLoading(status); + deferred.resolve(status); }).error(function (data, status, headers, config) { //TODO: Log the errors - document.getElementById("loading").style.visibility = "hidden"; + _setLoading(status); + deferred.resolve(status + "\n" + headers + "\n" + config); }); + + return deferred.promise; } /** @@ -143,16 +205,26 @@ * @private */ function _getTweetsFromLocal() { - document.getElementById("loading").style.visibility = "visible"; + _setLoading(true); + var deferred = $q.defer(); var url = "data/example-response.json"; $http.get(url).then(function (result) { - if(result.status == 200){ - //Copy result data to the private array - angular.copy(result.data,_tweets); - } - document.getElementById("loading").style.visibility = "hidden"; + setTimeout(function(){ + if(result.status == 200){ + //Copy result data to the private array + angular.copy(result.data,_tweets); + _setLoading(status); + deferred.resolve(result.status); + } else { + _setLoading(status); + deferred.resolve(result.status + "\n" + result.headers + "\n" + result.config); + } + }, 2000); + }); + + return deferred.promise; } /** @@ -276,5 +348,19 @@ tend: times[1] }; } + + function _getLoading(){ + return isLoading; + } + + function _setLoading(status){ + if (!status || status == 200) { + isLoading = false; + } else if (status) { + isLoading = true; + } else { + isLoading = false; + } + } } })(); \ No newline at end of file diff --git a/rest_service/src/main/resources/static/views/listView.html b/rest_service/src/main/resources/static/views/listView.html index 939a94b..ea3b71e 100644 --- a/rest_service/src/main/resources/static/views/listView.html +++ b/rest_service/src/main/resources/static/views/listView.html @@ -10,12 +10,6 @@

 

-
- - Status: Loading - -
-
diff --git a/rest_service/src/main/resources/static/views/mapView.html b/rest_service/src/main/resources/static/views/mapView.html index e8c7b0a..a797f7f 100644 --- a/rest_service/src/main/resources/static/views/mapView.html +++ b/rest_service/src/main/resources/static/views/mapView.html @@ -12,7 +12,7 @@
- +
@@ -35,16 +35,10 @@
-
- - Status: Loading - -
-
- +
@@ -62,7 +56,7 @@
-