diff --git a/docs/config.rst b/docs/config.rst index 042ae0d..ac3e35f 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -216,6 +216,8 @@ the form ``key = value``. Two items take a different form:, ``worlds`` and **You must specify at least one render** +.. _outputdir: + ``outputdir = ""`` This is the path to the output directory where the rendered tiles will be saved. @@ -250,11 +252,38 @@ the form ``key = value``. Two items take a different form:, ``worlds`` and observer to be used. The observer object is expected to have at least ``start``, ``add``, ``update``, and ``finish`` methods. - e.g.:: + If you want to specify an observer manually, try something like: + :: + from observer import ProgressBarObserver() observer = ProgressBarObserver() -.. _outputdir: + There are currently three observers available: ``LoggingObserver``, + ``ProgressBarObserver`` and ``JSObserver``. + + ``LoggingObserver`` + This gives the normal/older style output and is the default when output + is redirected to a file or when running on Windows + + ``ProgressBarObserver`` + This is used by default when the output is a terminal. Displays a text based + progress bar and some statistics. + + ``JSObserver`` + This will display render progress on the output map in the bottom right + corner of the screen. ``JSObserver`` must be invoked with two parameters. + + The first is output directory. For simplicity, specify this as ``outputdir`` + and place this line after setting ``outputdir = ""``. + + The second parameter is the minimum interval between progress updates in + seconds. Progress information won't be written to file or requested by + your web browser more frequently than this interval. + :: + + from observer import JSObserver + observer = JSObserver(outputdir, 10) + .. _renderdict: diff --git a/overviewer_core/data/js_src/util.js b/overviewer_core/data/js_src/util.js index 0e738da..3d6c36e 100644 --- a/overviewer_core/data/js_src/util.js +++ b/overviewer_core/data/js_src/util.js @@ -58,6 +58,10 @@ overviewer.util = { var coordsdiv = new overviewer.views.CoordboxView({tagName: 'DIV'}); coordsdiv.render(); + var progressdiv = new overviewer.views.ProgressView({tagName: 'DIV'}); + progressdiv.render(); + progressdiv.updateProgress(); + if (overviewer.collections.haveSigns) { var signs = new overviewer.views.SignControlView(); signs.registerEvents(signs); diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index 323c1fc..e66267e 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -108,7 +108,27 @@ overviewer.views.CoordboxView = Backbone.View.extend({ } }); - +overviewer.views.ProgressView = Backbone.View.extend({ + initialize: function() { + this.el.id = 'progressDiv'; + this.el.innerHTML = 'Current Render Progress'; + overviewer.map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(this.el); + this.hidden = true; + $.ajaxSetup({cache: false}); + }, + updateProgress: function() { + e = this; + $.getJSON('progress.js', null, function(d){ + e.el.hidden = false; + e.el.innerHTML = d['message']; + if (d.update > 0) { + setTimeout("e.updateProgress()", d.update); + } else { + e.el.hidden = true; + } + }); + } +}); /* GoogleMapView is responsible for dealing with the GoogleMaps API to create the */ diff --git a/overviewer_core/data/web_assets/overviewer.css b/overviewer_core/data/web_assets/overviewer.css index 0ae76e5..e1f1527 100644 --- a/overviewer_core/data/web_assets/overviewer.css +++ b/overviewer_core/data/web_assets/overviewer.css @@ -86,7 +86,7 @@ body { } -#link, #coordsDiv { +#link, #coordsDiv, #progressDiv { background-color: #fff; /* fallback */ background-color: rgba(255,255,255,0.55); border: 1px solid rgb(0, 0, 0); diff --git a/overviewer_core/observer.py b/overviewer_core/observer.py index c58a0d9..7db5061 100644 --- a/overviewer_core/observer.py +++ b/overviewer_core/observer.py @@ -17,6 +17,7 @@ import time import logging import progressbar import sys +import os class Observer(object): """Base class that defines the observer interface. @@ -164,3 +165,96 @@ class ProgressBarObserver(progressbar.ProgressBar, Observer): def _need_update(self): return self.get_current_value() - self.last_update > self.UPDATE_INTERVAL + +class JSObserver(Observer): + """Display progress on index.html using JavaScript + """ + + def __init__(self, outputdir, minrefresh=5): + """Initialise observer + outputdir must be set to the map output directory path + minrefresh specifies the minimum gap between requests, in seconds + """ + self.last_update = -11 + self.last_update_time = -1 + self._current_value = -1 + self.minrefresh = 1000*minrefresh + self.logfile = open(os.path.join(outputdir, "progress.js"), "w+", 0) + + def start(self, max_value): + self.logfile.seek(0) + self.logfile.write('{"message": "Rendering %d tiles", "update": %s}' % (max_value, self.minrefresh)) + self.logfile.truncate() + self.logfile.flush() + self.start_time=time.time() + self._set_max_value(max_value) + + def is_started(self): + return self.start_time is not None + + def finish(self): + """Signals the end of the processes, should be called after the + process is done. + """ + self.end_time = time.time() + duration = self.end_time - self.start_time + self.logfile.seek(0) + self.logfile.write('{"message": "Render completed in %dm %ds", "update": "false"}' % (duration//60, duration - duration//60)) + self.logfile.truncate() + self.logfile.close() + + def is_finished(self): + return self.end_time is not None + + def is_running(self): + return self.is_started() and not self.is_finished() + + def add(self, amount): + """Shortcut to update by increments instead of absolute values. Zero + amounts are ignored. + """ + if amount: + self.update(self.get_current_value() + amount) + + def update(self, current_value): + """Set the progress value. Should be between 0 and max_value. Returns + whether this update is actually displayed. + """ + self._current_value = current_value + if self._need_update(): + refresh = max(1500*(time.time() - self.last_update_time), self.minrefresh) + self.logfile.seek(0) + self.logfile.write('{"message": "Rendered %d of %d tiles (%d%%)", "update": %d }' % (self.get_current_value(), self.get_max_value(), self.get_percentage(), refresh)) + self.logfile.truncate() + self.logfile.flush() + self.last_update_time = time.time() + self.last_update = current_value + return True + return False + + def get_percentage(self): + """Get the current progress percentage. Assumes 100% if max_value is 0 + """ + if self.get_max_value() is 0: + return 100.0 + else: + return self.get_current_value() * 100.0 / self.get_max_value() + + def get_current_value(self): + return self._current_value + + def get_max_value(self): + return self._max_value + + def _set_max_value(self, max_value): + self._max_value = max_value + + def _need_update(self): + cur_val = self.get_current_value() + if cur_val < 100: + return cur_val - self.last_update > 10 + elif cur_val < 500: + return cur_val - self.last_update > 50 + else: + return cur_val - self.last_update > 100 + diff --git a/overviewer_core/settingsDefinition.py b/overviewer_core/settingsDefinition.py index d2e357c..cfb6539 100644 --- a/overviewer_core/settingsDefinition.py +++ b/overviewer_core/settingsDefinition.py @@ -45,7 +45,7 @@ from settingsValidators import * import util -from observer import ProgressBarObserver, LoggingObserver +from observer import ProgressBarObserver, LoggingObserver, JSObserver import platform import sys