diff --git a/.gitignore b/.gitignore index b6ac3fccbe..26c170bec9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ +*.pyc /bin/plovr*.jar -/bin/*.pyc /build/gh-pages /build/jsdoc-*-timestamp /build/lint-spec-timestamp diff --git a/build.py b/build.py index 0eb7877342..b19e28f9bd 100755 --- a/build.py +++ b/build.py @@ -9,27 +9,28 @@ import re import shutil import sys -import pake +from pake import Target, ifind, main, output, rule, target, variables, virtual + if sys.platform == 'win32': - pake.variables.GIT = 'C:/Program Files/Git/bin/git.exe' - pake.variables.GJSLINT = 'gjslint' # FIXME - pake.variables.JAVA = 'C:/Program Files/Java/jre7/bin/java.exe' - pake.variables.JSDOC = 'jsdoc' # FIXME - pake.variables.PHANTOMJS = 'phantomjs' # FIXME - pake.variables.PYTHON = 'C:/Python27/python.exe' + variables.GIT = 'C:/Program Files/Git/bin/git.exe' + variables.GJSLINT = 'gjslint' # FIXME + variables.JAVA = 'C:/Program Files/Java/jre7/bin/java.exe' + variables.JSDOC = 'jsdoc' # FIXME + variables.PHANTOMJS = 'phantomjs' # FIXME + variables.PYTHON = 'C:/Python27/python.exe' else: - pake.variables.GIT = 'git' - pake.variables.GJSLINT = 'gjslint' - pake.variables.JAVA = 'java' - pake.variables.JSDOC = 'jsdoc' - pake.variables.PHANTOMJS = 'phantomjs' - pake.variables.PYTHON = 'python' + variables.GIT = 'git' + variables.GJSLINT = 'gjslint' + variables.JAVA = 'java' + variables.JSDOC = 'jsdoc' + variables.PHANTOMJS = 'phantomjs' + variables.PYTHON = 'python' -pake.variables.BRANCH = pake.output('%(GIT)s', 'rev-parse', '--abbrev-ref', 'HEAD').strip() +variables.BRANCH = output('%(GIT)s', 'rev-parse', '--abbrev-ref', 'HEAD').strip() EXPORTS = [path - for path in pake.ifind('src') + for path in ifind('src') if path.endswith('.exports') if path != 'src/objectliterals.exports'] @@ -43,7 +44,7 @@ EXAMPLES = [path if path != 'examples/example-list.html'] EXAMPLES_SRC = [path - for path in pake.ifind('examples') + for path in ifind('examples') if path.endswith('.js') if not path.endswith('.combined.js') if path != 'examples/Jugl.js' @@ -54,11 +55,11 @@ INTERNAL_SRC = [ 'build/src/internal/src/types.js'] SPEC = [path - for path in pake.ifind('test/spec') + for path in ifind('test/spec') if path.endswith('.js')] SRC = [path - for path in pake.ifind('src/ol') + for path in ifind('src/ol') if path.endswith('.js')] PLOVR_JAR = 'bin/plovr-eba786b34df9.jar' @@ -75,50 +76,50 @@ def report_sizes(t): t.info(' compressed: %d bytes', len(stringio.getvalue())) -pake.virtual('all', 'build-all', 'build', 'examples') +virtual('all', 'build-all', 'build', 'examples') -pake.virtual('precommit', 'lint', 'build-all', 'test', 'doc', 'build', 'build-examples') +virtual('precommit', 'lint', 'build-all', 'test', 'build', 'build-examples', 'doc') -pake.virtual('build', 'build/ol.css', 'build/ol.js') +virtual('build', 'build/ol.css', 'build/ol.js') -@pake.target('build/ol.css', 'build/ol.js') +@target('build/ol.css', 'build/ol.js') def build_ol_css(t): t.touch() -@pake.target('build/ol.js', PLOVR_JAR, SRC, EXTERNAL_SRC, 'base.json', 'build/ol.json') +@target('build/ol.js', PLOVR_JAR, SRC, EXTERNAL_SRC, 'base.json', 'build/ol.json') def build_ol_js(t): t.output('%(JAVA)s', '-jar', PLOVR_JAR, 'build', 'build/ol.json') report_sizes(t) -pake.virtual('build-all', 'build/ol-all.js') +virtual('build-all', 'build/ol-all.js') -@pake.target('build/ol-all.js', PLOVR_JAR, SRC, INTERNAL_SRC, 'base.json', 'build/ol-all.json') +@target('build/ol-all.js', PLOVR_JAR, SRC, INTERNAL_SRC, 'base.json', 'build/ol-all.json') def build_ol_all_js(t): t.output('%(JAVA)s', '-jar', PLOVR_JAR, 'build', 'build/ol-all.json') -@pake.target('build/src/external/externs/types.js', 'bin/generate-exports.py', 'src/objectliterals.exports') +@target('build/src/external/externs/types.js', 'bin/generate-exports.py', 'src/objectliterals.exports') def build_src_external_externs_types_js(t): t.output('%(PYTHON)s', 'bin/generate-exports.py', '--externs', 'src/objectliterals.exports') -@pake.target('build/src/external/src/exports.js', 'bin/generate-exports.py', 'src/objectliterals.exports', EXPORTS) +@target('build/src/external/src/exports.js', 'bin/generate-exports.py', 'src/objectliterals.exports', EXPORTS) def build_src_external_src_exports_js(t): t.output('%(PYTHON)s', 'bin/generate-exports.py', '--exports', 'src/objectliterals.exports', EXPORTS) -@pake.target('build/src/external/src/types.js', 'bin/generate-exports', 'src/objectliterals.exports') +@target('build/src/external/src/types.js', 'bin/generate-exports', 'src/objectliterals.exports') def build_src_external_src_types_js(t): t.output('%(PYTHON)s', 'bin/generate-exports.py', '--typedef', 'src/objectliterals.exports') -@pake.target('build/src/internal/src/requireall.js', SRC) +@target('build/src/internal/src/requireall.js', SRC) def build_src_internal_src_requireall_js(t): requires = set(('goog.dom',)) for dependency in t.dependencies: @@ -131,23 +132,23 @@ def build_src_internal_src_requireall_js(t): f.write('goog.require(\'%s\');\n' % (require,)) -@pake.target('build/src/internal/src/types.js', 'bin/generate-exports.py', 'src/objectliterals.exports') +@target('build/src/internal/src/types.js', 'bin/generate-exports.py', 'src/objectliterals.exports') def build_src_internal_types_js(t): t.output('%(PYTHON)s', 'bin/generate-exports.py', '--typedef', 'src/objectliterals.exports') -pake.virtual('build-examples', 'examples', (path.replace('.html', '.combined.js') for path in EXAMPLES)) +virtual('build-examples', 'examples', (path.replace('.html', '.combined.js') for path in EXAMPLES)) -pake.virtual('examples', 'examples/example-list.js', (path.replace('.html', '.json') for path in EXAMPLES)) +virtual('examples', 'examples/example-list.js', (path.replace('.html', '.json') for path in EXAMPLES)) -@pake.target('examples/example-list.js', 'bin/exampleparser.py', EXAMPLES) +@target('examples/example-list.js', 'bin/exampleparser.py', EXAMPLES) def examples_examples_list_js(t): t.run('%(PYTHON)s', 'bin/exampleparser.py', 'examples', 'examples') -@pake.rule(r'\Aexamples/(?P.*).json\Z') +@rule(r'\Aexamples/(?P.*).json\Z') def examples_star_json(name, match): def action(t): content = json.dumps({ @@ -161,55 +162,55 @@ def examples_star_json(name, match): with open(t.name, 'w') as f: f.write(content) dependencies = [__file__, 'base.json'] - return pake.Target(name, action=action, dependencies=dependencies) + return Target(name, action=action, dependencies=dependencies) -@pake.rule(r'\Aexamples/(?P.*).combined.js\Z') +@rule(r'\Aexamples/(?P.*).combined.js\Z') def examples_star_combined_js(name, match): def action(t): t.output('%(JAVA)s', '-jar', PLOVR_JAR, 'build', 'examples/%(id)s.json' % match.groupdict()) report_sizes(t) dependencies = [PLOVR_JAR, SRC, INTERNAL_SRC, 'base.json', 'examples/%(id)s.js' % match.groupdict(), 'examples/%(id)s.json' % match.groupdict()] - return pake.Target(name, action=action, dependencies=dependencies) + return Target(name, action=action, dependencies=dependencies) -@pake.target('serve', PLOVR_JAR, INTERNAL_SRC, 'examples') +@target('serve', PLOVR_JAR, INTERNAL_SRC, 'examples') def serve(t): t.run('%(JAVA)s', '-jar', PLOVR_JAR, 'serve', glob.glob('build/*.json'), glob.glob('examples/*.json')) -@pake.target('serve-precommit', PLOVR_JAR, INTERNAL_SRC) +@target('serve-precommit', PLOVR_JAR, INTERNAL_SRC) def serve_precommit(t): t.run('%(JAVA)s', '-jar', PLOVR_JAR, 'serve', 'build/ol-all.json') -pake.virtual('lint', 'build/lint-src-timestamp', 'build/lint-spec-timestamp') +virtual('lint', 'build/lint-src-timestamp', 'build/lint-spec-timestamp') -@pake.target('build/lint-src-timestamp', SRC, INTERNAL_SRC, EXTERNAL_SRC, EXAMPLES_SRC) +@target('build/lint-src-timestamp', SRC, INTERNAL_SRC, EXTERNAL_SRC, EXAMPLES_SRC) def build_lint_src_timestamp(t): limited_doc_files = [path - for path in pake.ifind('externs', 'build/src/external/externs') + for path in ifind('externs', 'build/src/external/externs') if path.endswith('.js')] t.run('%(GJSLINT)s', '--strict', '--limited_doc_files=%s' % (','.join(limited_doc_files),), SRC, INTERNAL_SRC, EXTERNAL_SRC, EXAMPLES_SRC) t.touch() -@pake.target('build/lint-spec-timestamp', SPEC) +@target('build/lint-spec-timestamp', SPEC) def build_lint_spec_timestamp(t): t.run('%(GJSLINT)s', SPEC) t.touch() -pake.virtual('plovr', PLOVR_JAR) +virtual('plovr', PLOVR_JAR) -@pake.target(PLOVR_JAR, clean=False) +@target(PLOVR_JAR, clean=False) def plovr_jar(t): t.download('https://plovr.googlecode.com/files/' + os.path.basename(PLOVR_JAR), md5=PLOVR_JAR_MD5) -@pake.target('gh-pages', 'hostexamples', 'doc', phony=True) +@target('gh-pages', 'hostexamples', 'doc', phony=True) def gh_pages(t): with t.tempdir() as tempdir: t.run('%(GIT)s', 'clone', '--branch', 'gh-pages', 'git@github.com:openlayers/ol3.git', tempdir) @@ -222,16 +223,16 @@ def gh_pages(t): t.run('%(GIT)s', 'push', 'origin', 'gh-pages') -pake.virtual('doc', 'build/jsdoc-%(BRANCH)s-timestamp' % vars(pake.variables)) +virtual('doc', 'build/jsdoc-%(BRANCH)s-timestamp' % vars(variables)) -@pake.target('build/jsdoc-%(BRANCH)s-timestamp' % vars(pake.variables), SRC, pake.ifind('doc/template')) +@target('build/jsdoc-%(BRANCH)s-timestamp' % vars(variables), SRC, ifind('doc/template')) def jsdoc_BRANCH_timestamp(t): t.run('%(JSDOC)s', '-t', 'doc/template', '-r', 'src', '-d', 'build/gh-pages/%(BRANCH)s/apidoc') t.touch() -@pake.target('hostexamples', 'build', 'examples', phony=True) +@target('hostexamples', 'build', 'examples', phony=True) def hostexamples(t): t.makedirs('build/gh-pages/%(BRANCH)s/examples') t.makedirs('build/gh-pages/%(BRANCH)s/build') @@ -242,10 +243,10 @@ def hostexamples(t): t.cp('examples/example-list.js', 'examples/example-list.xml', 'examples/Jugl.js', 'build/gh-pages/%(BRANCH)s/examples/') -@pake.target('test', INTERNAL_SRC, phony=True) +@target('test', INTERNAL_SRC, phony=True) def test(t): t.run('%(PHANTOMJS)s', 'test/phantom-jasmine/run_jasmine_test.coffee', 'test/ol.html') if __name__ == '__main__': - pake.main() + main() diff --git a/examples/canvas-tiles.html b/examples/canvas-tiles.html new file mode 100644 index 0000000000..7340cdc3fc --- /dev/null +++ b/examples/canvas-tiles.html @@ -0,0 +1,38 @@ + + + + + + + + ol3 canvas tiles demo + + +

Canvas tiles example

+
The black grid tiles are generated on the client with an HTML5 canvas. Note that the tile coordinates are ol3 normalized tile coordinates (origin bottom left), not OSM tile coordinates (origin top left).
+ + + + + + + + + +
DOMWebGL
+
+

See the + canvas-tiles.js source + to see how this is done.

+
+
layers, stamen, canvas
+ + + diff --git a/examples/canvas-tiles.js b/examples/canvas-tiles.js new file mode 100644 index 0000000000..92c3f15046 --- /dev/null +++ b/examples/canvas-tiles.js @@ -0,0 +1,43 @@ +goog.require('ol.Collection'); +goog.require('ol.Coordinate'); +goog.require('ol.Map'); +goog.require('ol.Projection'); +goog.require('ol.RendererHint'); +goog.require('ol.layer.TileLayer'); +goog.require('ol.source.DebugTileSource'); +goog.require('ol.source.Stamen'); + + +var layers = new ol.Collection([ + new ol.layer.TileLayer({ + source: new ol.source.Stamen({ + provider: ol.source.StamenProvider.WATERCOLOR + }) + }), + new ol.layer.TileLayer({ + source: new ol.source.DebugTileSource({ + projection: ol.Projection.getFromCode('EPSG:3857'), + tileGrid: new ol.tilegrid.XYZ({ + maxZoom: 22 + }) + }) + }) +]); + +var webglMap = new ol.Map({ + view: new ol.View2D({ + center: ol.Projection.transformWithCodes( + new ol.Coordinate(-0.1275, 51.507222), 'EPSG:4326', 'EPSG:3857'), + zoom: 10 + }), + layers: layers, + renderer: ol.RendererHint.WEBGL, + target: 'webglMap' +}); + +var domMap = new ol.Map({ + renderer: ol.RendererHint.DOM, + target: 'domMap' +}); +domMap.bindTo('layers', webglMap); +domMap.bindTo('view', webglMap); diff --git a/examples/full-screen.js b/examples/full-screen.js index a02fc63e8b..0228a84c5c 100644 --- a/examples/full-screen.js +++ b/examples/full-screen.js @@ -4,6 +4,8 @@ goog.require('goog.debug.Logger.Level'); goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Map'); +goog.require('ol.RendererHints'); +goog.require('ol.View2D'); goog.require('ol.source.MapQuestOpenAerial'); @@ -17,8 +19,11 @@ var layer = new ol.layer.TileLayer({ source: new ol.source.MapQuestOpenAerial() }); var map = new ol.Map({ - center: new ol.Coordinate(0, 0), layers: new ol.Collection([layer]), + renderers: ol.RendererHints.createFromQueryData(), target: 'map', - zoom: 2 + view: new ol.View2D({ + center: new ol.Coordinate(0, 0), + zoom: 0 + }) }); diff --git a/examples/overlay-and-popup.js b/examples/overlay-and-popup.js index 4b9b9f7222..1e19bb425b 100644 --- a/examples/overlay-and-popup.js +++ b/examples/overlay-and-popup.js @@ -4,6 +4,8 @@ goog.require('goog.debug.Logger.Level'); goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Map'); +goog.require('ol.RendererHints'); +goog.require('ol.View2D'); goog.require('ol.overlay.Overlay'); goog.require('ol.source.MapQuestOpenAerial'); @@ -17,11 +19,15 @@ if (goog.DEBUG) { var layer = new ol.layer.TileLayer({ source: new ol.source.MapQuestOpenAerial() }); + var map = new ol.Map({ - center: new ol.Coordinate(0, 0), layers: new ol.Collection([layer]), + renderers: ol.RendererHints.createFromQueryData(), target: 'map', - zoom: 2 + view: new ol.View2D({ + center: new ol.Coordinate(0, 0), + zoom: 2 + }) }); // Vienna label diff --git a/examples/side-by-side.html b/examples/side-by-side.html index 2983cd69a8..6aa2307ef5 100644 --- a/examples/side-by-side.html +++ b/examples/side-by-side.html @@ -60,6 +60,10 @@ Visibility: v/V keys + + Animations: + j/l/m/x/L/M/X keys + Reset 0 key diff --git a/examples/side-by-side.js b/examples/side-by-side.js index d2e9817662..540a277e76 100644 --- a/examples/side-by-side.js +++ b/examples/side-by-side.js @@ -4,6 +4,8 @@ goog.require('goog.debug.Logger.Level'); goog.require('ol.Coordinate'); goog.require('ol.Map'); goog.require('ol.RendererHint'); +goog.require('ol.View2D'); +goog.require('ol.animation'); goog.require('ol.control.MousePosition'); goog.require('ol.interaction.Keyboard'); goog.require('ol.layer.TileLayer'); @@ -16,23 +18,32 @@ if (goog.DEBUG) { } +var LONDON = ol.Projection.transformWithCodes( + new ol.Coordinate(-0.12755, 51.507222), 'EPSG:4326', 'EPSG:3857'); +var MOSCOW = ol.Projection.transformWithCodes( + new ol.Coordinate(37.6178, 55.7517), 'EPSG:4326', 'EPSG:3857'); + var layer = new ol.layer.TileLayer({ source: new ol.source.MapQuestOpenAerial() }); -var domMap = new ol.Map({ +var view = new ol.View2D({ center: new ol.Coordinate(0, 0), + zoom: 1 +}); + +var domMap = new ol.Map({ layers: new ol.Collection([layer]), renderer: ol.RendererHint.DOM, target: 'domMap', - zoom: 1 + view: view }); domMap.getControls().push(new ol.control.MousePosition({ coordinateFormat: ol.Coordinate.toStringHDMS, projection: ol.Projection.getFromCode('EPSG:4326'), target: document.getElementById('domMousePosition'), - undefinedHtml: ' ' + undefinedHTML: ' ' })); var webglMap = new ol.Map({ @@ -40,17 +51,15 @@ var webglMap = new ol.Map({ target: 'webglMap' }); if (webglMap !== null) { - webglMap.bindTo('center', domMap); webglMap.bindTo('layers', domMap); - webglMap.bindTo('resolution', domMap); - webglMap.bindTo('rotation', domMap); + webglMap.bindTo('view', domMap); } webglMap.getControls().push(new ol.control.MousePosition({ coordinateFormat: ol.Coordinate.toStringHDMS, projection: ol.Projection.getFromCode('EPSG:4326'), target: document.getElementById('webglMousePosition'), - undefinedHtml: ' ' + undefinedHTML: ' ' })); var keyboardInteraction = new ol.interaction.Keyboard(); @@ -81,6 +90,47 @@ keyboardInteraction.addCallback('h', function() { keyboardInteraction.addCallback('H', function() { layer.setHue(layer.getHue() + (Math.PI / 5)); }); +keyboardInteraction.addCallback('j', function() { + var bounce = ol.animation.createBounce(2 * view.getResolution()); + domMap.addPreRenderFunction(bounce); + webglMap.addPreRenderFunction(bounce); +}); +keyboardInteraction.addCallback('l', function() { + var panFrom = ol.animation.createPanFrom(view.getCenter()); + domMap.addPreRenderFunction(panFrom); + webglMap.addPreRenderFunction(panFrom); + view.setCenter(LONDON); +}); +keyboardInteraction.addCallback('L', function() { + var start = Date.now(); + var duration = 5000; + var bounce = ol.animation.createBounce( + 2 * view.getResolution(), duration, start); + var panFrom = ol.animation.createPanFrom(view.getCenter(), duration, start); + var spin = ol.animation.createSpin(duration, 2, start); + var preRenderFunctions = [bounce, panFrom, spin]; + domMap.addPreRenderFunctions(preRenderFunctions); + webglMap.addPreRenderFunctions(preRenderFunctions); + view.setCenter(LONDON); +}); +keyboardInteraction.addCallback('m', function() { + var panFrom = ol.animation.createPanFrom(view.getCenter(), 1000); + domMap.addPreRenderFunction(panFrom); + webglMap.addPreRenderFunction(panFrom); + view.setCenter(MOSCOW); +}); +keyboardInteraction.addCallback('M', function() { + var start = Date.now(); + var duration = 5000; + var bounce = ol.animation.createBounce( + 2 * view.getResolution(), duration, start); + var panFrom = ol.animation.createPanFrom(view.getCenter(), duration, start); + var spin = ol.animation.createSpin(duration, -2, start); + var preRenderFunctions = [bounce, panFrom, spin]; + domMap.addPreRenderFunctions(preRenderFunctions); + webglMap.addPreRenderFunctions(preRenderFunctions); + view.setCenter(MOSCOW); +}); keyboardInteraction.addCallback('o', function() { layer.setOpacity(layer.getOpacity() - 0.1); }); @@ -88,7 +138,7 @@ keyboardInteraction.addCallback('O', function() { layer.setOpacity(layer.getOpacity() + 0.1); }); keyboardInteraction.addCallback('r', function() { - webglMap.setRotation(0); + view.setRotation(0); }); keyboardInteraction.addCallback('s', function() { layer.setSaturation(layer.getSaturation() - 0.1); @@ -100,4 +150,14 @@ keyboardInteraction.addCallback('S', function() { keyboardInteraction.addCallback('vV', function() { layer.setVisible(!layer.getVisible()); }); +keyboardInteraction.addCallback('x', function() { + var spin = ol.animation.createSpin(2000, 2); + domMap.addPreRenderFunction(spin); + webglMap.addPreRenderFunction(spin); +}); +keyboardInteraction.addCallback('X', function() { + var spin = ol.animation.createSpin(2000, -2); + domMap.addPreRenderFunction(spin); + webglMap.addPreRenderFunction(spin); +}); domMap.getInteractions().push(keyboardInteraction); diff --git a/examples/two-layers.js b/examples/two-layers.js index 53824ff367..8ab0550a7d 100644 --- a/examples/two-layers.js +++ b/examples/two-layers.js @@ -3,6 +3,7 @@ goog.require('ol.Coordinate'); goog.require('ol.Map'); goog.require('ol.Projection'); goog.require('ol.RendererHint'); +goog.require('ol.View2D'); goog.require('ol.layer.TileLayer'); goog.require('ol.source.BingMaps'); goog.require('ol.source.TileJSON'); @@ -11,7 +12,7 @@ goog.require('ol.source.TileJSON'); var layers = new ol.Collection([ new ol.layer.TileLayer({ source: new ol.source.BingMaps({ - key: 'Ak0kFwyFsvMr0dVwuaURTqKAXytSSN47KOdj4uVpaWBhK-DT6Zo-FeHCiJUL0tYL', + key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', style: ol.BingMapsStyle.AERIAL }) }), @@ -23,19 +24,19 @@ var layers = new ol.Collection([ ]); var webglMap = new ol.Map({ - center: ol.Projection.transformWithCodes( - new ol.Coordinate(-77.93255, 37.9555), 'EPSG:4326', 'EPSG:3857'), layers: layers, renderer: ol.RendererHint.WEBGL, target: 'webglMap', - zoom: 5 + view: new ol.View2D({ + center: ol.Projection.transformWithCodes( + new ol.Coordinate(-77.93255, 37.9555), 'EPSG:4326', 'EPSG:3857'), + zoom: 5 + }) }); var domMap = new ol.Map({ renderer: ol.RendererHint.DOM, target: 'domMap' }); -domMap.bindTo('center', webglMap); domMap.bindTo('layers', webglMap); -domMap.bindTo('resolution', webglMap); -domMap.bindTo('rotation', webglMap); +domMap.bindTo('view', webglMap); diff --git a/examples/wms-custom-proj.js b/examples/wms-custom-proj.js index a61ac2ebb3..8052557a0e 100644 --- a/examples/wms-custom-proj.js +++ b/examples/wms-custom-proj.js @@ -5,6 +5,8 @@ goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Map'); goog.require('ol.Projection'); +goog.require('ol.RendererHints'); +goog.require('ol.View2D'); goog.require('ol.source.TiledWMS'); @@ -50,12 +52,15 @@ var layers = new ol.Collection([ ]); var map = new ol.Map({ - center: new ol.Coordinate(660000, 190000), - projection: epsg21781, // By setting userProjection to the same as projection, we do not need // proj4js because we do not need any transforms. userProjection: epsg21781, layers: layers, + renderers: ol.RendererHints.createFromQueryData(), target: 'map', - zoom: 9 + view: new ol.View2D({ + projection: epsg21781, + center: new ol.Coordinate(660000, 190000), + zoom: 2 + }) }); diff --git a/examples/wms.js b/examples/wms.js index e475b8ab65..18788dd342 100644 --- a/examples/wms.js +++ b/examples/wms.js @@ -4,6 +4,7 @@ goog.require('goog.debug.Logger.Level'); goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Map'); +goog.require('ol.View2D'); goog.require('ol.source.MapQuestOpenAerial'); goog.require('ol.source.TiledWMS'); @@ -31,6 +32,8 @@ var map = new ol.Map({ renderer: ol.RendererHint.DOM, layers: layers, target: 'map', - center: new ol.Coordinate(-10997148, 4569099), - zoom: 4 + view: new ol.View2D({ + center: new ol.Coordinate(-10997148, 4569099), + zoom: 4 + }) }); diff --git a/license.txt b/license.txt index 20a789c3eb..cb829cc47d 100644 --- a/license.txt +++ b/license.txt @@ -1,4 +1,4 @@ -Copyright 2005-2012 OpenLayers Contributors. All rights reserved. See +Copyright 2005-2013 OpenLayers Contributors. All rights reserved. See authors.txt for full list. Redistribution and use in source and binary forms, with or without modification, diff --git a/readme.md b/readme.md index f01f7e9a08..e065102702 100644 --- a/readme.md +++ b/readme.md @@ -51,6 +51,6 @@ The `.html` file needs to include a script tag with the examples are `myexample.js` and `myexample.html` then `id` should be set to `myexample` in the `loader.js` URL. -`make serve` should be stopped and restarted for the -`loader.js?id=` script tag to refer to a valid URL. `make serve` +`build.py serve` should be stopped and restarted for the +`loader.js?id=` script tag to refer to a valid URL. `build.py serve` triggers the `examples` target which creates Plovr JSON file for each example. diff --git a/src/objectliterals.exports b/src/objectliterals.exports index d3f7330bb6..9ba2c520b3 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -7,7 +7,7 @@ @exportObjectLiteralProperty ol.control.MousePositionOptions.map ol.Map|undefined @exportObjectLiteralProperty ol.control.MousePositionOptions.projection ol.Projection|undefined @exportObjectLiteralProperty ol.control.MousePositionOptions.target Element|undefined -@exportObjectLiteralProperty ol.control.MousePositionOptions.undefinedHtml string|undefined +@exportObjectLiteralProperty ol.control.MousePositionOptions.undefinedHTML string|undefined @exportObjectLiteral ol.control.ZoomOptions @exportObjectLiteralProperty ol.control.ZoomOptions.delta number|undefined @@ -24,7 +24,6 @@ @exportObjectLiteralProperty ol.layer.LayerOptions.visible boolean|undefined @exportObjectLiteral ol.MapOptions -@exportObjectLiteralProperty ol.MapOptions.center ol.Coordinate|undefined @exportObjectLiteralProperty ol.MapOptions.controls ol.Collection|undefined @exportObjectLiteralProperty ol.MapOptions.doubleClickZoom boolean|undefined @exportObjectLiteralProperty ol.MapOptions.dragPan boolean|undefined @@ -32,22 +31,14 @@ @exportObjectLiteralProperty ol.MapOptions.keyboard boolean|undefined @exportObjectLiteralProperty ol.MapOptions.keyboardPanOffset number|undefined @exportObjectLiteralProperty ol.MapOptions.layers ol.Collection|undefined -@exportObjectLiteralProperty ol.MapOptions.maxResolution number|undefined @exportObjectLiteralProperty ol.MapOptions.mouseWheelZoom boolean|undefined @exportObjectLiteralProperty ol.MapOptions.mouseWheelZoomDelta number|undefined -@exportObjectLiteralProperty ol.MapOptions.numZoomLevels number|undefined -@exportObjectLiteralProperty ol.MapOptions.projection ol.Projection|string|undefined @exportObjectLiteralProperty ol.MapOptions.renderer ol.RendererHint|undefined @exportObjectLiteralProperty ol.MapOptions.renderers Array.|undefined -@exportObjectLiteralProperty ol.MapOptions.resolution number|undefined -@exportObjectLiteralProperty ol.MapOptions.resolutions Array.|undefined -@exportObjectLiteralProperty ol.MapOptions.rotate boolean|undefined @exportObjectLiteralProperty ol.MapOptions.shiftDragZoom boolean|undefined @exportObjectLiteralProperty ol.MapOptions.target Element|string -@exportObjectLiteralProperty ol.MapOptions.userProjection ol.Projection|string|undefined -@exportObjectLiteralProperty ol.MapOptions.zoom number|undefined +@exportObjectLiteralProperty ol.MapOptions.view ol.IView|undefined @exportObjectLiteralProperty ol.MapOptions.zoomDelta number|undefined -@exportObjectLiteralProperty ol.MapOptions.zoomFactor number|undefined @exportObjectLiteral ol.overlay.OverlayOptions @exportObjectLiteralProperty ol.overlay.OverlayOptions.coordinate ol.Coordinate|undefined @@ -71,3 +62,14 @@ @exportObjectLiteralProperty ol.source.TiledWMSOptions.projection ol.Projection|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.url string|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.urls Array.|undefined + +@exportObjectLiteral ol.View2DOptions +@exportObjectLiteralProperty ol.View2DOptions.center ol.Coordinate|undefined +@exportObjectLiteralProperty ol.View2DOptions.maxResolution number|undefined +@exportObjectLiteralProperty ol.View2DOptions.numZoomLevels number|undefined +@exportObjectLiteralProperty ol.View2DOptions.projection ol.Projection|string|undefined +@exportObjectLiteralProperty ol.View2DOptions.resolution number|undefined +@exportObjectLiteralProperty ol.View2DOptions.resolutions Array.|undefined +@exportObjectLiteralProperty ol.View2DOptions.rotation number|undefined +@exportObjectLiteralProperty ol.View2DOptions.zoom number|undefined +@exportObjectLiteralProperty ol.View2DOptions.zoomFactor number|undefined diff --git a/src/ol/animation.js b/src/ol/animation.js new file mode 100644 index 0000000000..e85fccbd5a --- /dev/null +++ b/src/ol/animation.js @@ -0,0 +1,109 @@ +// FIXME works for View2D only + +goog.provide('ol.animation'); + +goog.require('goog.fx.easing'); +goog.require('ol.PreRenderFunction'); +goog.require('ol.View2D'); +goog.require('ol.easing'); + + +/** + * @param {number} resolution Resolution. + * @param {number=} opt_duration Duration. + * @param {number=} opt_start Start. + * @param {function(number): number=} opt_easingFunction Easing function. + * @return {ol.PreRenderFunction} Pre-render function. + */ +ol.animation.createBounce = + function(resolution, opt_duration, opt_start, opt_easingFunction) { + var start = goog.isDef(opt_start) ? opt_start : Date.now(); + var duration = goog.isDef(opt_duration) ? opt_duration : 1000; + var easingFunction = goog.isDef(opt_easingFunction) ? + opt_easingFunction : ol.easing.upAndDown; + return function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = easingFunction((frameState.time - start) / duration); + var deltaResolution = resolution - frameState.view2DState.resolution; + frameState.animate = true; + frameState.view2DState.resolution += delta * deltaResolution; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } + }; +}; + + +/** + * @param {ol.Coordinate} source Source. + * @param {number=} opt_duration Duration. + * @param {number=} opt_start Start. + * @param {function(number): number=} opt_easingFunction Easing function. + * @return {ol.PreRenderFunction} Pre-render function. + */ +ol.animation.createPanFrom = + function(source, opt_duration, opt_start, opt_easingFunction) { + var start = goog.isDef(opt_start) ? opt_start : Date.now(); + var sourceX = source.x; + var sourceY = source.y; + var duration = goog.isDef(opt_duration) ? opt_duration : 1000; + var easingFunction = goog.isDef(opt_easingFunction) ? + opt_easingFunction : goog.fx.easing.inAndOut; + return function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easingFunction((frameState.time - start) / duration); + var deltaX = sourceX - frameState.view2DState.center.x; + var deltaY = sourceY - frameState.view2DState.center.y; + frameState.animate = true; + frameState.view2DState.center.x += delta * deltaX; + frameState.view2DState.center.y += delta * deltaY; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } + }; +}; + + +/** + * @param {number=} opt_duration Duration. + * @param {number=} opt_turns Turns. + * @param {number=} opt_start Start. + * @param {function(number): number=} opt_easingFunction Easing function. + * @return {ol.PreRenderFunction} Pre-render function. + */ +ol.animation.createSpin = + function(opt_duration, opt_turns, opt_start, opt_easingFunction) { + var start = goog.isDef(opt_start) ? opt_start : Date.now(); + var duration = goog.isDef(opt_duration) ? opt_duration : 1000; + var turns = goog.isDef(opt_turns) ? opt_turns : 1; + var deltaTheta = 2 * turns * Math.PI; + var easingFunction = goog.isDef(opt_easingFunction) ? + opt_easingFunction : goog.fx.easing.inAndOut; + return function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = easingFunction((frameState.time - start) / duration); + frameState.animate = true; + frameState.view2DState.rotation += delta * deltaTheta; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } + }; +}; diff --git a/src/ol/collection.js b/src/ol/collection.js index 454b166cf4..cce5dc87cd 100644 --- a/src/ol/collection.js +++ b/src/ol/collection.js @@ -130,7 +130,7 @@ ol.Collection.prototype.getAt = function(index) { * @return {number} Length. */ ol.Collection.prototype.getLength = function() { - return /** @type {number} */ this.get(ol.CollectionProperty.LENGTH); + return /** @type {number} */ (this.get(ol.CollectionProperty.LENGTH)); }; diff --git a/src/ol/color.js b/src/ol/color.js index 8791aeae7d..5d6286f6f9 100644 --- a/src/ol/color.js +++ b/src/ol/color.js @@ -46,3 +46,16 @@ ol.Color.createFromString = function(str, opt_a) { var a = opt_a || 255; return new ol.Color(rgb[0], rgb[1], rgb[2], a); }; + + +/** + * @param {ol.Color} color1 Color 1. + * @param {ol.Color} color2 Color 2. + * @return {boolean} Equals. + */ +ol.Color.equals = function(color1, color2) { + return (color1.r == color2.r && + color1.g == color2.g && + color1.b == color2.b && + color1.a == color2.a); +}; diff --git a/src/ol/control/attributioncontrol.js b/src/ol/control/attributioncontrol.js index 1fc9f939b8..27f5a7f836 100644 --- a/src/ol/control/attributioncontrol.js +++ b/src/ol/control/attributioncontrol.js @@ -2,6 +2,7 @@ // FIXME handle date line wrap // FIXME handle layer order // FIXME check clean-up code +// FIXME works for View2D only goog.provide('ol.control.Attribution'); @@ -14,6 +15,8 @@ goog.require('goog.style'); goog.require('ol.Collection'); goog.require('ol.CoverageArea'); goog.require('ol.TileCoverageArea'); +goog.require('ol.View2D'); +goog.require('ol.View2DProperty'); goog.require('ol.control.Control'); goog.require('ol.layer.Layer'); @@ -33,12 +36,6 @@ ol.control.Attribution = function(attributionOptions) { 'class': 'ol-attribution' }, this.ulElement_); - /** - * @private - * @type {Array.} - */ - this.layersListenerKeys_ = null; - /** * @private * @type {Object.} @@ -63,6 +60,18 @@ ol.control.Attribution = function(attributionOptions) { */ this.mapListenerKeys_ = null; + /** + * @private + * @type {Array.} + */ + this.layersListenerKeys_ = null; + + /** + * @private + * @type {Array.} + */ + this.viewListenerKeys_ = null; + goog.base(this, { element: element, map: attributionOptions.map, @@ -110,14 +119,17 @@ ol.control.Attribution.prototype.createAttributionElementsForLayer_ = var map = this.getMap(); var mapIsDef = map.isDef(); - var mapExtent = /** @type {ol.Extent} */ map.getExtent(); - var mapProjection = /** @type {ol.Projection} */ map.getProjection(); - var mapResolution = /** @type {number} */ map.getResolution(); - var layerVisible = layer.getVisible(); var attributionVisibilities; if (mapIsDef && layerVisible) { + var mapSize = /** @type {ol.Size} */ (map.getSize()); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + var mapExtent = view.getExtent(mapSize); + var mapProjection = /** @type {ol.Projection} */ (view.getProjection()); + var mapResolution = /** @type {number} */ (view.getResolution()); attributionVisibilities = this.getLayerAttributionVisiblities_( layer, mapExtent, mapResolution, mapProjection); } else { @@ -131,7 +143,7 @@ ol.control.Attribution.prototype.createAttributionElementsForLayer_ = var attributionElement = goog.dom.createElement(goog.dom.TagName.LI); attributionElement.innerHTML = attribution.getHtml(); - if (!map.isDef || + if (!mapIsDef || !layerVisible || goog.isNull(attributionVisibilities) || !attributionVisibilities[attributionKey]) { @@ -151,8 +163,8 @@ ol.control.Attribution.prototype.createAttributionElementsForLayer_ = /** * @param {ol.layer.Layer} layer Layer. - * @param {ol.Extent} mapExtent Map extent. - * @param {number} mapResolution Map resolution. + * @param {ol.Extent} mapExtent View extent. + * @param {number} mapResolution View resolution. * @param {ol.Projection} mapProjection Map projection. * @return {Object.} Attribution visibilities. * @private @@ -169,7 +181,7 @@ ol.control.Attribution.prototype.getLayerAttributionVisiblities_ = var mapZ; if (source instanceof ol.source.TileSource) { - var tileSource = /** @type {ol.source.TileSource} */ source; + var tileSource = /** @type {ol.source.TileSource} */ (source); var tileGrid = tileSource.getTileGrid(); mapZ = tileGrid.getZForResolution(mapResolution); } @@ -230,7 +242,7 @@ ol.control.Attribution.prototype.getLayerAttributionVisiblities_ = * @param {goog.events.Event} event Event. */ ol.control.Attribution.prototype.handleLayerLoad = function(event) { - var layer = /** @type {ol.layer.Layer} */ event.target; + var layer = /** @type {ol.layer.Layer} */ (event.target); this.createAttributionElementsForLayer_(layer); }; @@ -240,17 +252,8 @@ ol.control.Attribution.prototype.handleLayerLoad = function(event) { * @protected */ ol.control.Attribution.prototype.handleLayerVisibleChanged = function(event) { - - var map = this.getMap(); - var mapIsDef = map.isDef(); - var mapExtent = /** @type {ol.Extent} */ map.getExtent(); - var mapProjection = /** @type {ol.Projection} */ map.getProjection(); - var mapResolution = /** @type {number} */ map.getResolution(); - - var layer = /** @type {ol.layer.Layer} */ event.target; - - this.updateLayerAttributionsVisibility_( - layer, mapIsDef, mapExtent, mapResolution, mapProjection); + var layer = /** @type {ol.layer.Layer} */ (event.target); + this.updateLayerAttributionsVisibility_(layer); }; @@ -260,7 +263,7 @@ ol.control.Attribution.prototype.handleLayerVisibleChanged = function(event) { * @protected */ ol.control.Attribution.prototype.handleLayersAdd = function(collectionEvent) { - var layer = /** @type {ol.layer.Layer} */ collectionEvent.elem; + var layer = /** @type {ol.layer.Layer} */ (collectionEvent.elem); this.addLayer(layer); }; @@ -271,7 +274,7 @@ ol.control.Attribution.prototype.handleLayersAdd = function(collectionEvent) { */ ol.control.Attribution.prototype.handleLayersRemove = function(collectionEvent) { - var layer = /** @type {ol.layer.Layer} */ collectionEvent.elem; + var layer = /** @type {ol.layer.Layer} */ (collectionEvent.elem); this.removeLayer(layer); }; @@ -279,20 +282,26 @@ ol.control.Attribution.prototype.handleLayersRemove = /** * @protected */ -ol.control.Attribution.prototype.handleMapChanged = function() { - +ol.control.Attribution.prototype.handleMapViewChanged = function() { + if (!goog.isNull(this.viewListenerKeys_)) { + goog.array.forEach(this.viewListenerKeys_, goog.events.unlistenByKey); + this.viewListenerKeys_ = null; + } var map = this.getMap(); - var mapIsDef = map.isDef(); - var mapExtent = /** @type {ol.Extent} */ map.getExtent(); - var mapProjection = /** @type {ol.Projection} */ map.getProjection(); - var mapResolution = map.getResolution(); - - var layers = map.getLayers(); - layers.forEach(function(layer) { - this.updateLayerAttributionsVisibility_( - layer, mapIsDef, mapExtent, mapResolution, mapProjection); - }, this); - + goog.asserts.assert(!goog.isNull(map)); + var view = map.getView(); + if (!goog.isNull(view)) { + // FIXME works for View2D only + goog.asserts.assert(view instanceof ol.View2D); + this.viewListenerKeys_ = [ + goog.events.listen( + view, ol.Object.getChangedEventType(ol.View2DProperty.CENTER), + this.updateAttributions, false, this), + goog.events.listen( + view, ol.Object.getChangedEventType(ol.View2DProperty.RESOLUTION), + this.updateAttributions, false, this) + ]; + } }; @@ -359,35 +368,51 @@ ol.control.Attribution.prototype.setMap = function(map) { goog.base(this, 'setMap', map); if (!goog.isNull(map)) { this.mapListenerKeys_ = [ - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.CENTER), - this.handleMapChanged, false, this), goog.events.listen( map, ol.Object.getChangedEventType(ol.MapProperty.LAYERS), this.handleMapLayersChanged, false, this), - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.RESOLUTION), - this.handleMapChanged, false, this), goog.events.listen( map, ol.Object.getChangedEventType(ol.MapProperty.SIZE), - this.handleMapChanged, false, this) + this.updateAttributions, false, this), + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.VIEW), + this.updateAttributions, false, this) ]; + this.handleMapViewChanged(); this.handleMapLayersChanged(); } }; +/** + * @protected + */ +ol.control.Attribution.prototype.updateAttributions = function() { + + var map = this.getMap(); + var layers = map.getLayers(); + layers.forEach(function(layer) { + this.updateLayerAttributionsVisibility_(layer); + }, this); + +}; + + /** * @param {ol.layer.Layer} layer Layer. - * @param {boolean} mapIsDef Map is defined. - * @param {ol.Extent} mapExtent Map extent. - * @param {number} mapResolution Map resolution. - * @param {ol.Projection} mapProjection Map projection. * @private */ ol.control.Attribution.prototype.updateLayerAttributionsVisibility_ = - function(layer, mapIsDef, mapExtent, mapResolution, mapProjection) { - if (mapIsDef && layer.getVisible()) { + function(layer) { + var map = this.getMap(); + if (map.isDef() && layer.getVisible()) { + var mapSize = /** @type {ol.Size} */ (map.getSize()); + var view = map.getView(); + // FIXME works for View2D only + goog.asserts.assert(view instanceof ol.View2D); + var mapExtent = view.getExtent(mapSize); + var mapProjection = /** @type {ol.Projection} */ (view.getProjection()); + var mapResolution = /** @type {number} */ (view.getResolution()); var attributionVisibilities = this.getLayerAttributionVisiblities_( layer, mapExtent, mapResolution, mapProjection); goog.object.forEach( diff --git a/src/ol/control/mousepositioncontrol.js b/src/ol/control/mousepositioncontrol.js index 6466e73f36..ec45c3cabf 100644 --- a/src/ol/control/mousepositioncontrol.js +++ b/src/ol/control/mousepositioncontrol.js @@ -1,7 +1,9 @@ // FIXME should listen on appropriate pane, once it is defined +// FIXME works for View2D only goog.provide('ol.control.MousePosition'); +goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.style'); @@ -47,8 +49,20 @@ ol.control.MousePosition = function(mousePositionOptions) { * @private * @type {string} */ - this.undefinedHtml_ = goog.isDef(mousePositionOptions.undefinedHtml) ? - mousePositionOptions.undefinedHtml : ''; + this.undefinedHTML_ = goog.isDef(mousePositionOptions.undefinedHTML) ? + mousePositionOptions.undefinedHTML : ''; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = element.innerHTML; + + /** + * @private + * @type {ol.Projection} + */ + this.mapProjection_ = null; /** * @private @@ -58,23 +72,38 @@ ol.control.MousePosition = function(mousePositionOptions) { /** * @private - * @type {Array.} + * @type {ol.Projection} */ - this.listenerKeys_ = []; + this.renderedProjection_ = null; - this.handleMapProjectionChanged(); + /** + * @private + * @type {ol.Pixel} + */ + this.lastMouseMovePixel_ = null; + + /** + * @private + * @type {Array.} + */ + this.listenerKeys_ = null; }; goog.inherits(ol.control.MousePosition, ol.control.Control); /** + * @param {ol.MapEvent} mapEvent Map event. * @protected */ -ol.control.MousePosition.prototype.handleMapProjectionChanged = function() { - this.updateTransform_(); - // FIXME should we instead re-calculate using the last known mouse position? - this.element.innerHTML = this.undefinedHtml_; +ol.control.MousePosition.prototype.handleMapPostrender = function(mapEvent) { + var frameState = mapEvent.frameState; + if (goog.isNull(frameState)) { + this.mapProjection_ = null; + } else { + this.mapProjection_ = frameState.view2DState.projection; + } + this.updateHTML_(this.lastMouseMovePixel_); }; @@ -87,19 +116,8 @@ ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) { var eventPosition = goog.style.getRelativePosition( browserEvent, map.getViewport()); var pixel = new ol.Pixel(eventPosition.x, eventPosition.y); - var coordinate = map.getCoordinateFromPixel(pixel); - var html; - if (!goog.isNull(coordinate)) { - coordinate = this.transform_(coordinate); - if (goog.isDef(this.coordinateFormat_)) { - html = this.coordinateFormat_(coordinate); - } else { - html = coordinate.toString(); - } - } else { - html = this.undefinedHtml_; - } - this.element.innerHTML = html; + this.updateHTML_(pixel); + this.lastMouseMovePixel_ = pixel; }; @@ -108,7 +126,8 @@ ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) { * @protected */ ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) { - this.element.innerHTML = this.undefinedHtml_; + this.updateHTML_(null); + this.lastMouseMovePixel_ = null; }; @@ -123,34 +142,47 @@ ol.control.MousePosition.prototype.setMap = function(map) { goog.base(this, 'setMap', map); if (!goog.isNull(map)) { var viewport = map.getViewport(); - this.listenerKeys = [ - goog.events.listen(map, - ol.Object.getChangedEventType(ol.MapProperty.PROJECTION), - this.handleMapProjectionChanged, false, this), + this.listenerKeys_ = [ goog.events.listen(viewport, goog.events.EventType.MOUSEMOVE, this.handleMouseMove, false, this), goog.events.listen(viewport, goog.events.EventType.MOUSEOUT, - this.handleMouseOut, false, this) + this.handleMouseOut, false, this), + goog.events.listen(map, ol.MapEventType.POSTRENDER, + this.handleMapPostrender, false, this) ]; - this.updateTransform_(); } }; /** + * @param {?ol.Pixel} pixel Pixel. * @private */ -ol.control.MousePosition.prototype.updateTransform_ = function() { - var map = this.getMap(); - if (goog.isNull(map)) { - this.transform_ = ol.Projection.identityTransform; - } else { - var mapProjection = map.getProjection(); - if (!goog.isDef(mapProjection) || !goog.isDef(this.projection_)) { - this.transform_ = ol.Projection.identityTransform; - } else { - this.transform_ = - ol.Projection.getTransform(mapProjection, this.projection_); +ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { + var html = this.undefinedHTML_; + if (!goog.isNull(pixel)) { + if (this.renderedProjection_ != this.mapProjection_) { + if (goog.isDef(this.projection_)) { + this.transform_ = ol.Projection.getTransform( + this.mapProjection_, this.projection_); + } else { + this.transform_ = ol.Projection.identityTransform; + } + this.renderedProjection_ = this.mapProjection_; + } + var map = this.getMap(); + var coordinate = map.getCoordinateFromPixel(pixel); + if (!goog.isNull(coordinate)) { + coordinate = this.transform_(coordinate); + if (goog.isDef(this.coordinateFormat_)) { + html = this.coordinateFormat_(coordinate); + } else { + html = coordinate.toString(); + } } } + if (!goog.isDef(this.renderedHTML_) || html != this.renderedHTML_) { + this.element.innerHTML = html; + this.renderedHTML_ = html; + } }; diff --git a/src/ol/control/zoomcontrol.js b/src/ol/control/zoomcontrol.js index b429252ba0..82be362311 100644 --- a/src/ol/control/zoomcontrol.js +++ b/src/ol/control/zoomcontrol.js @@ -1,3 +1,5 @@ +// FIXME works for View2D only + goog.provide('ol.control.Zoom'); goog.require('goog.dom'); @@ -58,7 +60,9 @@ goog.inherits(ol.control.Zoom, ol.control.Control); ol.control.Zoom.prototype.handleIn_ = function(browserEvent) { // prevent #zoomIn anchor from getting appended to the url browserEvent.preventDefault(); - this.getMap().zoom(this.delta_); + var map = this.getMap(); + // FIXME works for View2D only + map.getView().zoom(map, this.delta_); }; @@ -69,5 +73,7 @@ ol.control.Zoom.prototype.handleIn_ = function(browserEvent) { ol.control.Zoom.prototype.handleOut_ = function(browserEvent) { // prevent #zoomOut anchor from getting appended to the url browserEvent.preventDefault(); - this.getMap().zoom(-this.delta_); + var map = this.getMap(); + // FIXME works for View2D only + map.getView().zoom(map, -this.delta_); }; diff --git a/src/ol/dom/dom.js b/src/ol/dom/dom.js new file mode 100644 index 0000000000..7fc85d93aa --- /dev/null +++ b/src/ol/dom/dom.js @@ -0,0 +1,86 @@ +// FIXME add tests for browser features (Modernizr?) +// FIXME implement Matrix Filter for IE < 9 + +goog.provide('ol.dom'); +goog.provide('ol.dom.BrowserFeature'); + +goog.require('goog.vec.Mat4'); + + +/** + * @enum {boolean} + */ +ol.dom.BrowserFeature = { + CAN_USE_CSS_TRANSFORM: false, + CAN_USE_CSS_TRANSFORM3D: true, + CAN_USE_MATRIX_FILTER: false +}; + + +/** + * @param {Element} element Element. + * @param {string} value Value. + */ +ol.dom.setTransform = function(element, value) { + var style = element.style; + style.WebkitTransform = value; + style.MozTransform = value; + style.OTransform = value; + style.transform = value; +}; + + +/** + * @param {Element} element Element. + * @param {goog.vec.Mat4.AnyType} transform Matrix. + * @param {number=} opt_precision Precision. + */ +ol.dom.transformElement2D = function(element, transform, opt_precision) { + // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer + // matrix3d() + var i; + if (ol.dom.BrowserFeature.CAN_USE_CSS_TRANSFORM3D) { + var value3D; + if (goog.isDef(opt_precision)) { + /** @type {Array.} */ + var strings3D = new Array(16); + for (i = 0; i < 16; ++i) { + strings3D[i] = transform[i].toFixed(opt_precision); + } + value3D = strings3D.join(','); + } else { + value3D = transform.join(','); + } + ol.dom.setTransform(element, 'matrix3d(' + value3D + ')'); + } else if (ol.dom.BrowserFeature.CAN_USE_CSS_TRANSFORM) { + /** @type {Array.} */ + var transform2D = [ + goog.vec.Mat4.getElement(transform, 0, 0), + goog.vec.Mat4.getElement(transform, 1, 0), + goog.vec.Mat4.getElement(transform, 0, 1), + goog.vec.Mat4.getElement(transform, 1, 1), + goog.vec.Mat4.getElement(transform, 0, 3), + goog.vec.Mat4.getElement(transform, 1, 3) + ]; + var value2D; + if (goog.isDef(opt_precision)) { + /** @type {Array.} */ + var strings2D = new Array(6); + for (i = 0; i < 6; ++i) { + strings2D[i] = transform2D[i].toFixed(opt_precision); + } + value2D = strings2D.join(','); + } else { + value2D = transform2D.join(','); + } + ol.dom.setTransform(element, 'matrix(' + value2D + ')'); + } else if (ol.dom.BrowserFeature.CAN_USE_MATRIX_FILTER) { + // http://msdn.microsoft.com/en-us/library/ms533014%28VS.85,loband%29.aspx + goog.asserts.assert(false); // FIXME + } else { + // FIXME check this code! + var style = element.style; + style.left = Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px'; + style.top = Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px'; + } +}; diff --git a/src/ol/easing.js b/src/ol/easing.js new file mode 100644 index 0000000000..74bd1c5210 --- /dev/null +++ b/src/ol/easing.js @@ -0,0 +1,14 @@ +goog.provide('ol.easing'); + + +/** + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + */ +ol.easing.upAndDown = function(t) { + if (t < 0.5) { + return goog.fx.easing.inAndOut(2 * t); + } else { + return 1 - goog.fx.easing.inAndOut(2 * (t - 0.5)); + } +}; diff --git a/src/ol/framestate.js b/src/ol/framestate.js new file mode 100644 index 0000000000..d84f4de831 --- /dev/null +++ b/src/ol/framestate.js @@ -0,0 +1,44 @@ +// FIXME add view3DState + +goog.provide('ol.FrameState'); +goog.provide('ol.PostRenderFunction'); +goog.provide('ol.PreRenderFunction'); + +goog.require('goog.vec.Mat4'); +goog.require('ol.Color'); +goog.require('ol.Coordinate'); +goog.require('ol.Extent'); +goog.require('ol.Size'); +goog.require('ol.TileQueue'); +goog.require('ol.View2DState'); +goog.require('ol.layer.LayerState'); + + +/** + * @typedef {{animate: boolean, + * backgroundColor: ol.Color, + * coordinateToPixelMatrix: goog.vec.Mat4.Number, + * extent: (null|ol.Extent), + * layersArray: Array., + * layerStates: Object., + * pixelToCoordinateMatrix: goog.vec.Mat4.Number, + * postRenderFunctions: Array., + * size: ol.Size, + * tileQueue: ol.TileQueue, + * time: number, + * view2DState: ol.View2DState, + * viewHints: Array.}} + */ +ol.FrameState; + + +/** + * @typedef {function(ol.Map, ?ol.FrameState): boolean} + */ +ol.PostRenderFunction; + + +/** + * @typedef {function(ol.Map, ?ol.FrameState): boolean} + */ +ol.PreRenderFunction; diff --git a/src/ol/imagetile.js b/src/ol/imagetile.js new file mode 100644 index 0000000000..12255034e3 --- /dev/null +++ b/src/ol/imagetile.js @@ -0,0 +1,138 @@ +goog.provide('ol.ImageTile'); + +goog.require('goog.array'); +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('ol.Tile'); +goog.require('ol.TileCoord'); +goog.require('ol.TileState'); + + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + */ +ol.ImageTile = function(tileCoord, src, crossOrigin) { + + goog.base(this, tileCoord); + + /** + * Image URI + * + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {Image} + */ + this.image_ = new Image(); + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Object.} + */ + this.imageByContext_ = {}; + + /** + * @private + * @type {Array.} + */ + this.imageListenerKeys_ = null; + +}; +goog.inherits(ol.ImageTile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.getImage = function(opt_context) { + if (goog.isDef(opt_context)) { + var image; + var key = goog.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (goog.object.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; + } +}; + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.getKey = function() { + return this.src_; +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.ImageTile.prototype.handleImageError_ = function() { + this.state = ol.TileState.ERROR; + this.unlistenImage_(); + this.dispatchChangeEvent(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.ImageTile.prototype.handleImageLoad_ = function() { + this.state = ol.TileState.LOADED; + this.unlistenImage_(); + this.dispatchChangeEvent(); +}; + + +/** + * Load not yet loaded URI. + */ +ol.ImageTile.prototype.load = function() { + if (this.state == ol.TileState.IDLE) { + this.state = ol.TileState.LOADING; + goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + this.image_.src = this.src_; + } +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.ImageTile.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; diff --git a/src/ol/interaction/dblclickzoominteraction.js b/src/ol/interaction/dblclickzoominteraction.js index c6e52fb1bc..b5e3e1e589 100644 --- a/src/ol/interaction/dblclickzoominteraction.js +++ b/src/ol/interaction/dblclickzoominteraction.js @@ -1,7 +1,10 @@ +// FIXME works for View2D only + goog.provide('ol.interaction.DblClickZoom'); goog.require('ol.MapBrowserEvent'); goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.View2D'); goog.require('ol.interaction.Interaction'); @@ -35,7 +38,10 @@ ol.interaction.DblClickZoom.prototype.handleMapBrowserEvent = var anchor = mapBrowserEvent.getCoordinate(); var delta = mapBrowserEvent.browserEvent.shiftKey ? -this.delta_ : this.delta_; - map.zoom(delta, anchor); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + view.zoom(map, delta, anchor); mapBrowserEvent.preventDefault(); browserEvent.preventDefault(); } diff --git a/src/ol/interaction/draginteraction.js b/src/ol/interaction/draginteraction.js index b8b019fb72..b41e548a6b 100644 --- a/src/ol/interaction/draginteraction.js +++ b/src/ol/interaction/draginteraction.js @@ -89,6 +89,7 @@ ol.interaction.Drag.prototype.handleMapBrowserEvent = if (!map.isDef()) { return; } + var view = map.getView(); var browserEvent = mapBrowserEvent.browserEvent; if (this.dragging_) { if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DRAG) { @@ -109,9 +110,9 @@ ol.interaction.Drag.prototype.handleMapBrowserEvent = this.startY = browserEvent.clientY; this.deltaX = 0; this.deltaY = 0; - this.startCenter = /** @type {!ol.Coordinate} */ map.getCenter(); + this.startCenter = /** @type {!ol.Coordinate} */ (view.getCenter()); this.startCoordinate = /** @type {ol.Coordinate} */ - mapBrowserEvent.getCoordinate(); + (mapBrowserEvent.getCoordinate()); var handled = this.handleDragStart(mapBrowserEvent); if (handled) { this.dragging_ = true; diff --git a/src/ol/interaction/dragpaninteraction.js b/src/ol/interaction/dragpaninteraction.js index d3a2c2fa7d..3fcc4d4157 100644 --- a/src/ol/interaction/dragpaninteraction.js +++ b/src/ol/interaction/dragpaninteraction.js @@ -1,7 +1,12 @@ +// FIXME works for View2D only + goog.provide('ol.interaction.DragPan'); +goog.require('goog.asserts'); goog.require('ol.Coordinate'); goog.require('ol.MapBrowserEvent'); +goog.require('ol.View2D'); +goog.require('ol.ViewHint'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Drag'); @@ -31,17 +36,28 @@ goog.inherits(ol.interaction.DragPan, ol.interaction.Drag); */ ol.interaction.DragPan.prototype.handleDrag = function(mapBrowserEvent) { var map = mapBrowserEvent.map; - var resolution = map.getResolution(); - var rotation = map.getRotation(); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + var resolution = view.getResolution(); + var rotation = view.getRotation(); var delta = new ol.Coordinate(-resolution * this.deltaX, resolution * this.deltaY); - if (map.canRotate() && goog.isDef(rotation)) { - delta.rotate(rotation); - } + delta.rotate(rotation); var newCenter = new ol.Coordinate( this.startCenter.x + delta.x, this.startCenter.y + delta.y); map.requestRenderFrame(); - map.setCenter(newCenter); + view.setCenter(newCenter); +}; + + +/** + * @inheritDoc + */ +ol.interaction.DragPan.prototype.handleDragEnd = function(mapBrowserEvent) { + var map = mapBrowserEvent.map; + map.requestRenderFrame(); + map.getView().setHint(ol.ViewHint.PANNING, -1); }; @@ -51,7 +67,9 @@ ol.interaction.DragPan.prototype.handleDrag = function(mapBrowserEvent) { ol.interaction.DragPan.prototype.handleDragStart = function(mapBrowserEvent) { var browserEvent = mapBrowserEvent.browserEvent; if (this.condition_(browserEvent)) { - mapBrowserEvent.map.requestRenderFrame(); + var map = mapBrowserEvent.map; + map.requestRenderFrame(); + map.getView().setHint(ol.ViewHint.PANNING, 1); return true; } else { return false; diff --git a/src/ol/interaction/dragrotateandzoominteraction.js b/src/ol/interaction/dragrotateandzoominteraction.js index 0fa9992f65..314ee9227f 100644 --- a/src/ol/interaction/dragrotateandzoominteraction.js +++ b/src/ol/interaction/dragrotateandzoominteraction.js @@ -1,7 +1,10 @@ +// FIXME works for View2D only + goog.provide('ol.interaction.DragRotateAndZoom'); goog.require('goog.math.Vec2'); goog.require('ol.MapBrowserEvent'); +goog.require('ol.View2D'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Drag'); @@ -50,12 +53,15 @@ ol.interaction.DragRotateAndZoom.prototype.handleDrag = browserEvent.offsetX - size.width / 2, size.height / 2 - browserEvent.offsetY); var theta = Math.atan2(delta.y, delta.x); + var resolution = this.startRatio_ * delta.magnitude(); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); // FIXME the calls to map.rotate and map.zoomToResolution should use // map.withFrozenRendering but an assertion fails :-( - map.rotate(this.startRotation_, -theta); - var resolution = this.startRatio_ * delta.magnitude(); - map.zoomToResolution(resolution); + view.rotate(map, this.startRotation_, -theta); + view.zoomToResolution(map, resolution); }; @@ -66,14 +72,15 @@ ol.interaction.DragRotateAndZoom.prototype.handleDragStart = function(mapBrowserEvent) { var browserEvent = mapBrowserEvent.browserEvent; var map = mapBrowserEvent.map; - if (map.canRotate() && this.condition_(browserEvent)) { - var resolution = map.getResolution(); + var view = map.getView().getView2D(); + if (this.condition_(browserEvent)) { + var resolution = view.getResolution(); var size = map.getSize(); var delta = new goog.math.Vec2( browserEvent.offsetX - size.width / 2, size.height / 2 - browserEvent.offsetY); var theta = Math.atan2(delta.y, delta.x); - this.startRotation_ = (map.getRotation() || 0) + theta; + this.startRotation_ = (view.getRotation() || 0) + theta; this.startRatio_ = resolution / delta.magnitude(); map.requestRenderFrame(); return true; diff --git a/src/ol/interaction/dragrotateinteraction.js b/src/ol/interaction/dragrotateinteraction.js index d574a72a64..2b6e12bbbb 100644 --- a/src/ol/interaction/dragrotateinteraction.js +++ b/src/ol/interaction/dragrotateinteraction.js @@ -1,6 +1,7 @@ goog.provide('ol.interaction.DragRotate'); goog.require('ol.MapBrowserEvent'); +goog.require('ol.View2D'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Drag'); @@ -42,8 +43,11 @@ ol.interaction.DragRotate.prototype.handleDrag = function(mapBrowserEvent) { var theta = Math.atan2( size.height / 2 - offset.y, offset.x - size.width / 2); + // FIXME supports View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); - map.rotate(this.startRotation_, -theta); + view.rotate(map, this.startRotation_, -theta); }; @@ -54,15 +58,17 @@ ol.interaction.DragRotate.prototype.handleDragStart = function(mapBrowserEvent) { var browserEvent = mapBrowserEvent.browserEvent; var map = mapBrowserEvent.map; - if (browserEvent.isMouseActionButton() && this.condition_(browserEvent) && - map.canRotate()) { + // FIXME supports View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + if (browserEvent.isMouseActionButton() && this.condition_(browserEvent)) { map.requestRenderFrame(); var size = map.getSize(); var offset = mapBrowserEvent.getPixel(); var theta = Math.atan2( size.height / 2 - offset.y, offset.x - size.width / 2); - this.startRotation_ = (map.getRotation() || 0) + theta; + this.startRotation_ = (view.getRotation() || 0) + theta; return true; } else { return false; diff --git a/src/ol/interaction/dragzoominteraction.js b/src/ol/interaction/dragzoominteraction.js index 367b80000f..a64437acda 100644 --- a/src/ol/interaction/dragzoominteraction.js +++ b/src/ol/interaction/dragzoominteraction.js @@ -1,4 +1,5 @@ // FIXME draw drag box +// FIXME works for View2D only goog.provide('ol.interaction.DragZoom'); @@ -63,7 +64,15 @@ ol.interaction.DragZoom.prototype.handleDragEnd = var extent = ol.Extent.boundingExtent( this.startCoordinate, mapBrowserEvent.getCoordinate()); - map.fitExtent(extent); + map.withFrozenRendering(function() { + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + var mapSize = /** @type {ol.Size} */ (map.getSize()); + view.fitExtent(extent, mapSize); + // FIXME we should preserve rotation + view.setRotation(0); + }); } }; diff --git a/src/ol/interaction/keyboardinteraction.js b/src/ol/interaction/keyboardinteraction.js index 4a104b48a0..8057b858ff 100644 --- a/src/ol/interaction/keyboardinteraction.js +++ b/src/ol/interaction/keyboardinteraction.js @@ -43,7 +43,7 @@ ol.interaction.Keyboard.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { var keyEvent = /** @type {goog.events.KeyEvent} */ - mapBrowserEvent.browserEvent; + (mapBrowserEvent.browserEvent); var callback = this.charCodeCallbacks_[keyEvent.charCode]; if (callback) { callback(); diff --git a/src/ol/interaction/keyboardpaninteraction.js b/src/ol/interaction/keyboardpaninteraction.js index 044b376f40..384b7d9d4c 100644 --- a/src/ol/interaction/keyboardpaninteraction.js +++ b/src/ol/interaction/keyboardpaninteraction.js @@ -1,7 +1,10 @@ +// FIXME works for View2D only + goog.provide('ol.interaction.KeyboardPan'); goog.require('goog.events.KeyCodes'); goog.require('goog.events.KeyHandler.EventType'); +goog.require('ol.View2D'); goog.require('ol.interaction.Interaction'); @@ -32,14 +35,17 @@ ol.interaction.KeyboardPan.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { var keyEvent = /** @type {goog.events.KeyEvent} */ - mapBrowserEvent.browserEvent; + (mapBrowserEvent.browserEvent); var keyCode = keyEvent.keyCode; if (keyCode == goog.events.KeyCodes.DOWN || keyCode == goog.events.KeyCodes.LEFT || keyCode == goog.events.KeyCodes.RIGHT || keyCode == goog.events.KeyCodes.UP) { var map = mapBrowserEvent.map; - var resolution = map.getResolution(); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + var resolution = view.getResolution(); var delta; var mapUnitsDelta = resolution * this.pixelDelta_; if (keyCode == goog.events.KeyCodes.DOWN) { @@ -52,10 +58,10 @@ ol.interaction.KeyboardPan.prototype.handleMapBrowserEvent = goog.asserts.assert(keyCode == goog.events.KeyCodes.UP); delta = new ol.Coordinate(0, mapUnitsDelta); } - var oldCenter = map.getCenter(); + var oldCenter = view.getCenter(); var newCenter = new ol.Coordinate( oldCenter.x + delta.x, oldCenter.y + delta.y); - map.setCenter(newCenter); + view.setCenter(newCenter); keyEvent.preventDefault(); mapBrowserEvent.preventDefault(); } diff --git a/src/ol/interaction/keyboardzoominteraction.js b/src/ol/interaction/keyboardzoominteraction.js index d02249755b..3075754ca7 100644 --- a/src/ol/interaction/keyboardzoominteraction.js +++ b/src/ol/interaction/keyboardzoominteraction.js @@ -1,7 +1,10 @@ +// FIXME works for View2D only + goog.provide('ol.interaction.KeyboardZoom'); goog.require('goog.events.KeyCodes'); goog.require('goog.events.KeyHandler.EventType'); +goog.require('ol.View2D'); goog.require('ol.interaction.Interaction'); @@ -23,12 +26,15 @@ ol.interaction.KeyboardZoom.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { var keyEvent = /** @type {goog.events.KeyEvent} */ - mapBrowserEvent.browserEvent; + (mapBrowserEvent.browserEvent); var charCode = keyEvent.charCode; if (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0)) { var map = mapBrowserEvent.map; var delta = (charCode == '+'.charCodeAt(0)) ? 4 : -4; - map.zoom(delta); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + view.zoom(map, delta); keyEvent.preventDefault(); mapBrowserEvent.preventDefault(); } diff --git a/src/ol/interaction/mousewheelzoominteraction.js b/src/ol/interaction/mousewheelzoominteraction.js index 0e439742c3..7e3c1769ac 100644 --- a/src/ol/interaction/mousewheelzoominteraction.js +++ b/src/ol/interaction/mousewheelzoominteraction.js @@ -1,8 +1,11 @@ +// FIXME works for View2D only + goog.provide('ol.interaction.MouseWheelZoom'); goog.require('goog.events.MouseWheelEvent'); goog.require('goog.events.MouseWheelHandler.EventType'); goog.require('ol.MapBrowserEvent'); +goog.require('ol.View2D'); @@ -32,12 +35,15 @@ ol.interaction.MouseWheelZoom.prototype.handleMapBrowserEvent = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL) { var map = mapBrowserEvent.map; var mouseWheelEvent = /** @type {goog.events.MouseWheelEvent} */ - mapBrowserEvent.browserEvent; + (mapBrowserEvent.browserEvent); goog.asserts.assert(mouseWheelEvent instanceof goog.events.MouseWheelEvent); var anchor = mapBrowserEvent.getCoordinate(); var delta = mouseWheelEvent.deltaY < 0 ? this.delta_ : -this.delta_; + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); - map.zoom(delta, anchor); + view.zoom(map, delta, anchor); mapBrowserEvent.preventDefault(); mouseWheelEvent.preventDefault(); } diff --git a/src/ol/iview.js b/src/ol/iview.js new file mode 100644 index 0000000000..5b7663bd1d --- /dev/null +++ b/src/ol/iview.js @@ -0,0 +1,27 @@ +goog.provide('ol.IView'); + +goog.require('ol.IView2D'); +goog.require('ol.IView3D'); + + + +/** + * Interface for views. + * @interface + */ +ol.IView = function() { +}; + + +/** + * @return {ol.IView2D} View2D. + */ +ol.IView.prototype.getView2D = function() { +}; + + +/** + * @return {ol.IView3D} View3D. + */ +ol.IView.prototype.getView3D = function() { +}; diff --git a/src/ol/iview2d.js b/src/ol/iview2d.js new file mode 100644 index 0000000000..e8dcb0c4db --- /dev/null +++ b/src/ol/iview2d.js @@ -0,0 +1,59 @@ +goog.provide('ol.IView2D'); +goog.provide('ol.View2DState'); + +goog.require('ol.Coordinate'); +goog.require('ol.Extent'); +goog.require('ol.Projection'); + + +/** + * @typedef {{center: ol.Coordinate, + * projection: ol.Projection, + * resolution: number, + * rotation: number}} + */ +ol.View2DState; + + + +/** + * Interface for views. + * @interface + */ +ol.IView2D = function() { +}; + + +/** + * @return {ol.Coordinate|undefined} Map center. + */ +ol.IView2D.prototype.getCenter = function() { +}; + + +/** + * @return {ol.Projection|undefined} Map projection. + */ +ol.IView2D.prototype.getProjection = function() { +}; + + +/** + * @return {number|undefined} Map resolution. + */ +ol.IView2D.prototype.getResolution = function() { +}; + + +/** + * @return {number|undefined} Map rotation. + */ +ol.IView2D.prototype.getRotation = function() { +}; + + +/** + * @return {ol.View2DState} View2D state. + */ +ol.IView2D.prototype.getView2DState = function() { +}; diff --git a/src/ol/iview3d.js b/src/ol/iview3d.js new file mode 100644 index 0000000000..90153214b5 --- /dev/null +++ b/src/ol/iview3d.js @@ -0,0 +1,12 @@ +goog.provide('ol.IView3D'); + + + +/** + * Interface for views. + * @interface + */ +ol.IView3D = function() { +}; + + diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index 277127b7bc..cb06bed1ba 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -1,5 +1,6 @@ goog.provide('ol.layer.Layer'); goog.provide('ol.layer.LayerProperty'); +goog.provide('ol.layer.LayerState'); goog.require('goog.events'); goog.require('goog.events.EventType'); @@ -21,6 +22,18 @@ ol.layer.LayerProperty = { }; +/** + * @typedef {{brightness: number, + * contrast: number, + * hue: number, + * opacity: number, + * ready: boolean, + * saturation: number, + * visible: boolean}} + */ +ol.layer.LayerState; + + /** * @constructor @@ -71,7 +84,7 @@ ol.layer.Layer.prototype.dispatchLoadEvent_ = function() { * @return {number} Brightness. */ ol.layer.Layer.prototype.getBrightness = function() { - return /** @type {number} */ this.get(ol.layer.LayerProperty.BRIGHTNESS); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.BRIGHTNESS)); }; goog.exportProperty( ol.layer.Layer.prototype, @@ -83,7 +96,7 @@ goog.exportProperty( * @return {number} Contrast. */ ol.layer.Layer.prototype.getContrast = function() { - return /** @type {number} */ this.get(ol.layer.LayerProperty.CONTRAST); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.CONTRAST)); }; goog.exportProperty( ol.layer.Layer.prototype, @@ -95,7 +108,7 @@ goog.exportProperty( * @return {number} Hue. */ ol.layer.Layer.prototype.getHue = function() { - return /** @type {number} */ this.get(ol.layer.LayerProperty.HUE); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE)); }; goog.exportProperty( ol.layer.Layer.prototype, @@ -103,11 +116,34 @@ goog.exportProperty( ol.layer.Layer.prototype.getHue); +/** + * @return {ol.layer.LayerState} Layer state. + */ +ol.layer.Layer.prototype.getLayerState = function() { + var brightness = this.getBrightness(); + var contrast = this.getContrast(); + var hue = this.getHue(); + var opacity = this.getOpacity(); + var ready = this.isReady(); + var saturation = this.getSaturation(); + var visible = this.getVisible(); + return { + brightness: goog.isDef(brightness) ? brightness : 0, + contrast: goog.isDef(contrast) ? contrast : 1, + hue: goog.isDef(hue) ? hue : 0, + opacity: goog.isDef(opacity) ? opacity : 1, + ready: ready, + saturation: goog.isDef(saturation) ? saturation : 1, + visible: goog.isDef(visible) ? visible : true + }; +}; + + /** * @return {number} Opacity. */ ol.layer.Layer.prototype.getOpacity = function() { - return /** @type {number} */ this.get(ol.layer.LayerProperty.OPACITY); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY)); }; goog.exportProperty( ol.layer.Layer.prototype, @@ -119,7 +155,7 @@ goog.exportProperty( * @return {number} Saturation. */ ol.layer.Layer.prototype.getSaturation = function() { - return /** @type {number} */ this.get(ol.layer.LayerProperty.SATURATION); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.SATURATION)); }; goog.exportProperty( ol.layer.Layer.prototype, @@ -139,7 +175,7 @@ ol.layer.Layer.prototype.getSource = function() { * @return {boolean} Visible. */ ol.layer.Layer.prototype.getVisible = function() { - return /** @type {boolean} */ this.get(ol.layer.LayerProperty.VISIBLE); + return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE)); }; goog.exportProperty( ol.layer.Layer.prototype, diff --git a/src/ol/layer/tilelayer.js b/src/ol/layer/tilelayer.js index c4d38313d7..878185e77f 100644 --- a/src/ol/layer/tilelayer.js +++ b/src/ol/layer/tilelayer.js @@ -20,5 +20,5 @@ goog.inherits(ol.layer.TileLayer, ol.layer.Layer); * @return {ol.source.TileSource} Source. */ ol.layer.TileLayer.prototype.getTileSource = function() { - return /** @type {ol.source.TileSource} */ this.getSource(); + return /** @type {ol.source.TileSource} */ (this.getSource()); }; diff --git a/src/ol/map.js b/src/ol/map.js index 3e0296695d..f82253eeaf 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -3,10 +3,11 @@ // FIXME add tilt and height? goog.provide('ol.Map'); -goog.provide('ol.MapEventType'); goog.provide('ol.MapProperty'); goog.provide('ol.RendererHint'); +goog.provide('ol.RendererHints'); +goog.require('goog.Uri.QueryData'); goog.require('goog.array'); goog.require('goog.async.AnimationDelay'); goog.require('goog.debug.Logger'); @@ -22,22 +23,24 @@ goog.require('goog.events.KeyHandler.EventType'); goog.require('goog.events.MouseWheelEvent'); goog.require('goog.events.MouseWheelHandler'); goog.require('goog.events.MouseWheelHandler.EventType'); -goog.require('goog.functions'); goog.require('goog.object'); goog.require('ol.BrowserFeature'); goog.require('ol.Collection'); goog.require('ol.Color'); -goog.require('ol.Constraints'); goog.require('ol.Coordinate'); goog.require('ol.Extent'); +goog.require('ol.FrameState'); goog.require('ol.MapBrowserEvent'); goog.require('ol.Object'); goog.require('ol.Pixel'); -goog.require('ol.Projection'); goog.require('ol.ResolutionConstraint'); goog.require('ol.RotationConstraint'); goog.require('ol.Size'); +goog.require('ol.TileQueue'); goog.require('ol.TransformFunction'); +goog.require('ol.View'); +goog.require('ol.View2D'); +goog.require('ol.View2DState'); goog.require('ol.control.Attribution'); goog.require('ol.control.Zoom'); goog.require('ol.interaction.DblClickZoom'); @@ -87,26 +90,14 @@ ol.DEFAULT_RENDERER_HINTS = [ ]; -/** - * @enum {string} - */ -ol.MapEventType = { - POSTRENDER: 'postrender' -}; - - /** * @enum {string} */ ol.MapProperty = { BACKGROUND_COLOR: 'backgroundColor', - CENTER: 'center', LAYERS: 'layers', - PROJECTION: 'projection', - RESOLUTION: 'resolution', - ROTATION: 'rotation', SIZE: 'size', - USER_PROJECTION: 'userProjection' + VIEW: 'view' }; @@ -130,18 +121,6 @@ ol.Map = function(mapOptions) { var mapOptionsInternal = ol.Map.createOptionsInternal(mapOptions); - /** - * @type {ol.TransformFunction} - * @private - */ - this.userToMapTransform_ = ol.Projection.identityTransform; - - /** - * @type {ol.TransformFunction} - * @private - */ - this.mapToUserTransform_ = ol.Projection.cloneTransform; - /** * @private * @type {goog.async.AnimationDelay} @@ -150,6 +129,24 @@ ol.Map = function(mapOptions) { new goog.async.AnimationDelay(this.renderFrame_, undefined, this); this.registerDisposable(this.animationDelay_); + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.coordinateToPixelMatrix_ = goog.vec.Mat4.createNumber(); + + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber(); + + /** + * @private + * @type {?ol.FrameState} + */ + this.frameState_ = null; + /** * @private * @type {number} @@ -168,12 +165,6 @@ ol.Map = function(mapOptions) { */ this.target_ = mapOptionsInternal.target; - /** - * @private - * @type {ol.Constraints} - */ - this.constraints_ = mapOptionsInternal.constraints; - /** * @private * @type {Element} @@ -253,13 +244,29 @@ ol.Map = function(mapOptions) { goog.events.listen(this.viewportSizeMonitor_, goog.events.EventType.RESIZE, this.handleBrowserWindowResize, false, this); - goog.events.listen( - this, ol.Object.getChangedEventType(ol.MapProperty.PROJECTION), - this.handleProjectionChanged, false, this); + /** + * @private + * @type {Array.} + */ + this.preRenderFunctions_ = []; - goog.events.listen( - this, ol.Object.getChangedEventType(ol.MapProperty.USER_PROJECTION), - this.handleUserProjectionChanged, false, this); + /** + * @private + * @type {Array.} + */ + this.postRenderFunctions_ = []; + + /** + * @private + * @type {function(this: ol.Map)} + */ + this.handlePostRender_ = goog.bind(this.handlePostRender, this); + + /** + * @private + * @type {ol.TileQueue} + */ + this.tileQueue_ = new ol.TileQueue(goog.bind(this.getTilePriority, this)); this.setValues(mapOptionsInternal.values); @@ -278,10 +285,22 @@ goog.inherits(ol.Map, ol.Object); /** - * @return {boolean} Can rotate. + * @param {ol.PreRenderFunction} preRenderFunction Pre-render function. */ -ol.Map.prototype.canRotate = function() { - return this.renderer_.canRotate(); +ol.Map.prototype.addPreRenderFunction = function(preRenderFunction) { + this.requestRenderFrame(); + this.preRenderFunctions_.push(preRenderFunction); +}; + + +/** + * @param {Array.} preRenderFunctions + * Pre-render functions. + */ +ol.Map.prototype.addPreRenderFunctions = function(preRenderFunctions) { + this.requestRenderFrame(); + Array.prototype.push.apply( + this.preRenderFunctions_, preRenderFunctions); }; @@ -295,30 +314,6 @@ ol.Map.prototype.disposeInternal = function() { }; -/** - * @param {ol.Extent} extent Extent. - */ -ol.Map.prototype.fitExtent = function(extent) { - this.withFrozenRendering(function() { - this.setCenter(extent.getCenter()); - var resolution = this.getResolutionForExtent(extent); - resolution = this.constraints_.resolution(resolution, 0); - this.setResolution(resolution); - if (this.canRotate()) { - this.setRotation(0); - } - }, this); -}; - - -/** - * @param {ol.Extent} userExtent Extent in user projection. - */ -ol.Map.prototype.fitUserExtent = function(userExtent) { - this.fitExtent(userExtent.transform(this.userToMapTransform_)); -}; - - /** * Freeze rendering. */ @@ -340,18 +335,6 @@ goog.exportProperty( ol.Map.prototype.getBackgroundColor); -/** - * @return {ol.Coordinate|undefined} Center. - */ -ol.Map.prototype.getCenter = function() { - return /** @type {ol.Coordinate} */ this.get(ol.MapProperty.CENTER); -}; -goog.exportProperty( - ol.Map.prototype, - 'getCenter', - ol.Map.prototype.getCenter); - - /** * @return {Element} Container. */ @@ -373,25 +356,13 @@ ol.Map.prototype.getControls = function() { * @return {ol.Coordinate} Coordinate. */ ol.Map.prototype.getCoordinateFromPixel = function(pixel) { - return this.isDef() ? this.renderer_.getCoordinateFromPixel(pixel) : null; -}; - - -/** - * @return {ol.Extent|undefined} Extent. - */ -ol.Map.prototype.getExtent = function() { - if (this.isDef()) { - var center = this.getCenter(); - var resolution = this.getResolution(); - var size = this.getSize(); - var minX = center.x - resolution * size.width / 2; - var minY = center.y - resolution * size.height / 2; - var maxX = center.x + resolution * size.width / 2; - var maxY = center.y + resolution * size.height / 2; - return new ol.Extent(minX, minY, maxX, maxY); + var frameState = this.frameState_; + if (goog.isNull(frameState)) { + return null; } else { - return undefined; + var vec3 = [pixel.x, pixel.y, 0]; + goog.vec.Mat4.multVec3(frameState.pixelToCoordinateMatrix, vec3, vec3); + return new ol.Coordinate(vec3[0], vec3[1]); } }; @@ -414,99 +385,25 @@ ol.Map.prototype.getLayers = function() { /** * @param {ol.Coordinate} coordinate Coordinate. - * @return {ol.Pixel|undefined} Pixel. + * @return {ol.Pixel} Pixel. */ ol.Map.prototype.getPixelFromCoordinate = function(coordinate) { - if (this.isDef()) { - return this.renderer_.getPixelFromCoordinate(coordinate); + var frameState = this.frameState_; + if (goog.isNull(frameState)) { + return null; } else { - return undefined; + var vec3 = [coordinate.x, coordinate.y, 0]; + goog.vec.Mat4.multVec3(frameState.coordinateToPixelMatrix, vec3, vec3); + return new ol.Pixel(vec3[0], vec3[1]); } }; -/** - * @return {ol.Projection|undefined} Projection. - */ -ol.Map.prototype.getProjection = function() { - return /** @type {ol.Projection} */ this.get(ol.MapProperty.PROJECTION); -}; -goog.exportProperty( - ol.Map.prototype, - 'getProjection', - ol.Map.prototype.getProjection); - - -/** - * @return {number|undefined} Resolution. - */ -ol.Map.prototype.getResolution = function() { - return /** @type {number} */ this.get(ol.MapProperty.RESOLUTION); -}; -goog.exportProperty( - ol.Map.prototype, - 'getResolution', - ol.Map.prototype.getResolution); - - -/** - * @param {ol.Extent} extent Extent. - * @return {number|undefined} Resolution. - */ -ol.Map.prototype.getResolutionForExtent = function(extent) { - var size = this.getSize(); - if (goog.isDef(size)) { - var xResolution = (extent.maxX - extent.minX) / size.width; - var yResolution = (extent.maxY - extent.minY) / size.height; - return Math.max(xResolution, yResolution); - } else { - return undefined; - } -}; - - -/** - * @return {ol.Extent} Rotated extent. - */ -ol.Map.prototype.getRotatedExtent = function() { - goog.asserts.assert(this.isDef()); - var center = /** @type {!ol.Coordinate} */ this.getCenter(); - var resolution = this.getResolution(); - var rotation = this.getRotation() || 0; - var size = this.getSize(); - var xScale = resolution * size.width / 2; - var yScale = resolution * size.height / 2; - var corners = [ - new ol.Coordinate(-xScale, -yScale), - new ol.Coordinate(-xScale, yScale), - new ol.Coordinate(xScale, -yScale), - new ol.Coordinate(xScale, yScale) - ]; - goog.array.forEach(corners, function(corner) { - corner.rotate(rotation); - corner.add(center); - }); - return ol.Extent.boundingExtent.apply(null, corners); -}; - - -/** - * @return {number} Rotation. - */ -ol.Map.prototype.getRotation = function() { - return /** @type {number} */ this.get(ol.MapProperty.ROTATION) || 0; -}; -goog.exportProperty( - ol.Map.prototype, - 'getRotation', - ol.Map.prototype.getRotation); - - /** * @return {ol.Size|undefined} Size. */ ol.Map.prototype.getSize = function() { - return /** @type {ol.Size|undefined} */ this.get(ol.MapProperty.SIZE); + return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE)); }; goog.exportProperty( ol.Map.prototype, @@ -515,42 +412,15 @@ goog.exportProperty( /** - * @return {ol.Coordinate|undefined} Center in user projection. + * @return {ol.View} View. */ -ol.Map.prototype.getUserCenter = function() { - var center = this.getCenter(); - if (goog.isDef(center)) { - return this.mapToUserTransform_(center); - } else { - return undefined; - } -}; - - -/** - * @return {ol.Extent|undefined} Extent in user projection. - */ -ol.Map.prototype.getUserExtent = function() { - var extent = this.getExtent(); - if (goog.isDef(extent)) { - return extent.transform(this.mapToUserTransform_); - } else { - return undefined; - } -}; - - -/** - * @return {ol.Projection|undefined} Projection. - */ -ol.Map.prototype.getUserProjection = function() { - return /** @type {ol.Projection} */ this.get( - ol.MapProperty.USER_PROJECTION); +ol.Map.prototype.getView = function() { + return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW)); }; goog.exportProperty( ol.Map.prototype, - 'getUserProjection', - ol.Map.prototype.getUserProjection); + 'getView', + ol.Map.prototype.getView); /** @@ -571,6 +441,24 @@ ol.Map.prototype.getOverlayContainer = function() { }; +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + * @return {number|undefined} Tile priority. + */ +ol.Map.prototype.getTilePriority = function(tile, tileCenter, tileResolution) { + if (goog.isNull(this.frameState_)) { + return undefined; + } else { + var center = this.frameState_.view2DState.center; + var deltaX = tileCenter.x - center.x; + var deltaY = tileCenter.y - center.y; + return Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; + } +}; + + /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @param {string=} opt_type Type. @@ -587,7 +475,7 @@ ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) { * @private */ ol.Map.prototype.handleControlsAdd_ = function(collectionEvent) { - var control = /** @type {ol.control.Control} */ collectionEvent.elem; + var control = /** @type {ol.control.Control} */ (collectionEvent.elem); control.setMap(this); }; @@ -597,7 +485,7 @@ ol.Map.prototype.handleControlsAdd_ = function(collectionEvent) { * @private */ ol.Map.prototype.handleControlsRemove_ = function(collectionEvent) { - var control = /** @type {ol.control.Control} */ collectionEvent.elem; + var control = /** @type {ol.control.Control} */ (collectionEvent.elem); control.setMap(null); }; @@ -608,7 +496,7 @@ ol.Map.prototype.handleControlsRemove_ = function(collectionEvent) { ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { var interactions = this.getInteractions(); var interactionsArray = /** @type {Array.} */ - interactions.getArray(); + (interactions.getArray()); if (this.dispatchEvent(mapBrowserEvent) !== false) { for (var i = interactionsArray.length - 1; i >= 0; i--) { var interaction = interactionsArray[i]; @@ -624,16 +512,16 @@ ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { /** * @protected */ -ol.Map.prototype.handleProjectionChanged = function() { - this.recalculateTransforms_(); -}; - - -/** - * @protected - */ -ol.Map.prototype.handleUserProjectionChanged = function() { - this.recalculateTransforms_(); +ol.Map.prototype.handlePostRender = function() { + this.tileQueue_.reprioritize(); // FIXME only call if needed + this.tileQueue_.loadMoreTiles(); + goog.array.forEach( + this.postRenderFunctions_, + function(postRenderFunction) { + postRenderFunction(this, this.frameState_); + }, + this); + this.postRenderFunctions_.length = 0; }; @@ -650,31 +538,12 @@ ol.Map.prototype.handleBrowserWindowResize = function() { * @return {boolean} Is defined. */ ol.Map.prototype.isDef = function() { - return goog.isDefAndNotNull(this.getCenter()) && - goog.isDef(this.getResolution()) && + var view = this.getView(); + return goog.isDef(view) && view.isDef() && goog.isDefAndNotNull(this.getSize()); }; -/** - * @private - */ -ol.Map.prototype.recalculateTransforms_ = function() { - var projection = this.getProjection(); - var userProjection = this.getUserProjection(); - if (goog.isDefAndNotNull(projection) && - goog.isDefAndNotNull(userProjection)) { - this.mapToUserTransform_ = ol.Projection.getTransform( - projection, userProjection); - this.userToMapTransform_ = ol.Projection.getTransform( - userProjection, projection); - } else { - this.mapToUserTransform_ = ol.Projection.cloneTransform; - this.userToMapTransform_ = ol.Projection.identityTransform; - } -}; - - /** * Render. */ @@ -708,28 +577,98 @@ ol.Map.prototype.requestRenderFrame = function() { * @private */ ol.Map.prototype.renderFrame_ = function(time) { + + var i; + if (this.freezeRenderingCount_ != 0) { return; } + if (goog.DEBUG) { this.logger.info('renderFrame_'); } - this.renderer_.renderFrame(time); - this.dirty_ = false; - if (goog.DEBUG) { - this.logger.info('postrender'); + + var size = this.getSize(); + var layers = this.getLayers(); + var layersArray = goog.isDef(layers) ? + /** @type {Array.} */ (layers.getArray()) : undefined; + var view = this.getView(); + var view2D = goog.isDef(view) ? this.getView().getView2D() : undefined; + /** @type {?ol.FrameState} */ + var frameState = null; + if (goog.isDef(layersArray) && goog.isDef(size) && goog.isDef(view2D) && + view2D.isDef()) { + var backgroundColor = this.getBackgroundColor(); + var viewHints = view.getHints(); + var layerStates = {}; + goog.array.forEach(layersArray, function(layer) { + layerStates[goog.getUid(layer)] = layer.getLayerState(); + }); + var view2DState = view2D.getView2DState(); + frameState = { + animate: false, + backgroundColor: goog.isDef(backgroundColor) ? + backgroundColor : new ol.Color(1, 1, 1, 1), + coordinateToPixelMatrix: this.coordinateToPixelMatrix_, + extent: null, + layersArray: layersArray, + layerStates: layerStates, + pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_, + postRenderFunctions: [], + size: size, + tileQueue: this.tileQueue_, + view2DState: view2DState, + viewHints: viewHints, + time: time + }; } - this.dispatchEvent(ol.MapEventType.POSTRENDER); -}; + this.preRenderFunctions_ = goog.array.filter( + this.preRenderFunctions_, + function(preRenderFunction) { + return preRenderFunction(this, frameState); + }, + this); + + if (!goog.isNull(frameState)) { + // FIXME works for View2D only + var center = view2DState.center; + var resolution = view2DState.resolution; + var rotation = view2DState.rotation; + var x = resolution * size.width / 2; + var y = resolution * size.height / 2; + var corners = [ + new ol.Coordinate(-x, -y), + new ol.Coordinate(-x, y), + new ol.Coordinate(x, -y), + new ol.Coordinate(x, y) + ]; + var corner; + for (i = 0; i < 4; ++i) { + corner = corners[i]; + corner.rotate(rotation); + corner.add(center); + } + frameState.extent = ol.Extent.boundingExtent.apply(null, corners); + } + + this.renderer_.renderFrame(frameState); + + if (!goog.isNull(frameState)) { + if (frameState.animate) { + this.requestRenderFrame(); + } + Array.prototype.push.apply( + this.postRenderFunctions_, frameState.postRenderFunctions); + } + this.frameState_ = frameState; + this.dirty_ = false; + + this.dispatchEvent( + new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState)); + + goog.global.setTimeout(this.handlePostRender_, 0); -/** - * @param {number|undefined} rotation Rotation. - * @param {number} delta Delta. - */ -ol.Map.prototype.rotate = function(rotation, delta) { - rotation = this.constraints_.rotation(rotation, delta); - this.setRotation(rotation); }; @@ -745,18 +684,6 @@ goog.exportProperty( ol.Map.prototype.setBackgroundColor); -/** - * @param {ol.Coordinate|undefined} center Center. - */ -ol.Map.prototype.setCenter = function(center) { - this.set(ol.MapProperty.CENTER, center); -}; -goog.exportProperty( - ol.Map.prototype, - 'setCenter', - ol.Map.prototype.setCenter); - - /** * @param {ol.Collection} layers Layers. */ @@ -769,42 +696,6 @@ goog.exportProperty( ol.Map.prototype.setLayers); -/** - * @param {ol.Projection} projection Projection. - */ -ol.Map.prototype.setProjection = function(projection) { - this.set(ol.MapProperty.PROJECTION, projection); -}; -goog.exportProperty( - ol.Map.prototype, - 'setProjection', - ol.Map.prototype.setProjection); - - -/** - * @param {number|undefined} resolution Resolution. - */ -ol.Map.prototype.setResolution = function(resolution) { - this.set(ol.MapProperty.RESOLUTION, resolution); -}; -goog.exportProperty( - ol.Map.prototype, - 'setResolution', - ol.Map.prototype.setResolution); - - -/** - * @param {number|undefined} rotation Rotation. - */ -ol.Map.prototype.setRotation = function(rotation) { - this.set(ol.MapProperty.ROTATION, rotation); -}; -goog.exportProperty( - ol.Map.prototype, - 'setRotation', - ol.Map.prototype.setRotation); - - /** * @param {ol.Size} size Size. */ @@ -818,23 +709,15 @@ goog.exportProperty( /** - * @param {ol.Coordinate} userCenter Center in user projection. + * @param {ol.IView} view View. */ -ol.Map.prototype.setUserCenter = function(userCenter) { - this.setCenter(this.userToMapTransform_(userCenter)); -}; - - -/** - * @param {ol.Projection} userProjection User projection. - */ -ol.Map.prototype.setUserProjection = function(userProjection) { - this.set(ol.MapProperty.USER_PROJECTION, userProjection); +ol.Map.prototype.setView = function(view) { + this.set(ol.MapProperty.VIEW, view); }; goog.exportProperty( ol.Map.prototype, - 'setUserProjection', - ol.Map.prototype.setUserProjection); + 'setView', + ol.Map.prototype.setView); /** @@ -863,53 +746,9 @@ ol.Map.prototype.withFrozenRendering = function(f, opt_obj) { }; -/** - * @private - * @param {number|undefined} resolution Resolution to go to. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - */ -ol.Map.prototype.zoom_ = function(resolution, opt_anchor) { - if (goog.isDefAndNotNull(resolution) && goog.isDefAndNotNull(opt_anchor)) { - var anchor = opt_anchor; - var oldCenter = /** @type {!ol.Coordinate} */ this.getCenter(); - var oldResolution = this.getResolution(); - var x = anchor.x - resolution * (anchor.x - oldCenter.x) / oldResolution; - var y = anchor.y - resolution * (anchor.y - oldCenter.y) / oldResolution; - var center = new ol.Coordinate(x, y); - this.withFrozenRendering(function() { - this.setCenter(center); - this.setResolution(resolution); - }, this); - } else { - this.setResolution(resolution); - } -}; - - -/** - * @param {number} delta Delta from previous zoom level. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - */ -ol.Map.prototype.zoom = function(delta, opt_anchor) { - var resolution = this.constraints_.resolution(this.getResolution(), delta); - this.zoom_(resolution, opt_anchor); -}; - - -/** - * @param {number|undefined} resolution Resolution to go to. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - */ -ol.Map.prototype.zoomToResolution = function(resolution, opt_anchor) { - resolution = this.constraints_.resolution(resolution, 0); - this.zoom_(resolution, opt_anchor); -}; - - /** * @typedef {{controls: ol.Collection, * interactions: ol.Collection, - * constraints: ol.Constraints, * rendererConstructor: * function(new: ol.renderer.Map, Element, ol.Map), * target: Element, @@ -929,25 +768,11 @@ ol.Map.createOptionsInternal = function(mapOptions) { */ var values = {}; - if (goog.isDef(mapOptions.center)) { - values[ol.MapProperty.CENTER] = mapOptions.center; - } - values[ol.MapProperty.LAYERS] = goog.isDef(mapOptions.layers) ? mapOptions.layers : new ol.Collection(); - values[ol.MapProperty.PROJECTION] = ol.Projection.createProjection( - mapOptions.projection, 'EPSG:3857'); - - if (goog.isDef(mapOptions.resolution)) { - values[ol.MapProperty.RESOLUTION] = mapOptions.resolution; - } else if (goog.isDef(mapOptions.zoom)) { - values[ol.MapProperty.RESOLUTION] = - ol.Projection.EPSG_3857_HALF_SIZE / (128 << mapOptions.zoom); - } - - values[ol.MapProperty.USER_PROJECTION] = ol.Projection.createProjection( - mapOptions.userProjection, 'EPSG:4326'); + values[ol.MapProperty.VIEW] = goog.isDef(mapOptions.view) ? + mapOptions.view : new ol.View2D(); /** * @type {function(new: ol.renderer.Map, Element, ol.Map)} @@ -982,11 +807,6 @@ ol.Map.createOptionsInternal = function(mapOptions) { } } - /** - * @type {ol.Constraints} - */ - var constraints = ol.Map.createConstraints_(mapOptions); - /** * @type {ol.Collection} */ @@ -1013,7 +833,6 @@ ol.Map.createOptionsInternal = function(mapOptions) { var target = goog.dom.getElement(mapOptions.target); return { - constraints: constraints, controls: controls, interactions: interactions, rendererConstructor: rendererConstructor, @@ -1024,40 +843,6 @@ ol.Map.createOptionsInternal = function(mapOptions) { }; -/** - * @private - * @param {ol.MapOptions} mapOptions Map options. - * @return {ol.Constraints} Map constraints. - */ -ol.Map.createConstraints_ = function(mapOptions) { - var resolutionConstraint; - if (goog.isDef(mapOptions.resolutions)) { - resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions( - mapOptions.resolutions); - } else { - var maxResolution, numZoomLevels, zoomFactor; - if (goog.isDef(mapOptions.maxResolution) && - goog.isDef(mapOptions.numZoomLevels) && - goog.isDef(mapOptions.zoomFactor)) { - maxResolution = mapOptions.maxResolution; - numZoomLevels = mapOptions.numZoomLevels; - zoomFactor = mapOptions.zoomFactor; - } else { - maxResolution = ol.Projection.EPSG_3857_HALF_SIZE / 128; - // number of steps we want between two data resolutions - var numSteps = 4; - numZoomLevels = 29 * numSteps; - zoomFactor = Math.exp(Math.log(2) / numSteps); - } - resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( - zoomFactor, maxResolution, numZoomLevels - 1); - } - // FIXME rotation constraint is not configurable at the moment - var rotationConstraint = ol.RotationConstraint.none; - return new ol.Constraints(resolutionConstraint, rotationConstraint); -}; - - /** * @private * @param {ol.MapOptions} mapOptions Map options. @@ -1139,3 +924,20 @@ ol.Map.createInteractions_ = function(mapOptions) { return interactions; }; + + +/** + * @param {goog.Uri.QueryData=} opt_queryData Query data. + * @return {Array.} Renderer hints. + */ +ol.RendererHints.createFromQueryData = function(opt_queryData) { + var queryData = goog.isDef(opt_queryData) ? + opt_queryData : new goog.Uri.QueryData(goog.global.location.search); + if (queryData.containsKey('renderers')) { + return queryData.get('renderers').split(','); + } else if (queryData.containsKey('renderer')) { + return [queryData.get('renderer')]; + } else { + return ol.DEFAULT_RENDERER_HINTS; + } +}; diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js index 63b3c956cd..c39f838928 100644 --- a/src/ol/mapbrowserevent.js +++ b/src/ol/mapbrowserevent.js @@ -270,7 +270,9 @@ ol.MapBrowserEventHandler.prototype.drag_ = function(browserEvent) { }; -/** @override */ +/** + * FIXME empty description for jsdoc + */ ol.MapBrowserEventHandler.prototype.disposeInternal = function() { var element = this.map_.getViewport(); goog.events.unlisten(element, diff --git a/src/ol/mapevent.js b/src/ol/mapevent.js index df4e04adc0..581a27f0e1 100644 --- a/src/ol/mapevent.js +++ b/src/ol/mapevent.js @@ -1,6 +1,16 @@ goog.provide('ol.MapEvent'); +goog.provide('ol.MapEventType'); goog.require('goog.events.Event'); +goog.require('ol.FrameState'); + + +/** + * @enum {string} + */ +ol.MapEventType = { + POSTRENDER: 'postrender' +}; @@ -9,8 +19,9 @@ goog.require('goog.events.Event'); * @extends {goog.events.Event} * @param {string} type Event type. * @param {ol.Map} map Map. + * @param {?ol.FrameState=} opt_frameState Frame state. */ -ol.MapEvent = function(type, map) { +ol.MapEvent = function(type, map, opt_frameState) { goog.base(this, type); @@ -24,6 +35,11 @@ ol.MapEvent = function(type, map) { */ this.defaultPrevented = false; + /** + * @type {?ol.FrameState} + */ + this.frameState = goog.isDef(opt_frameState) ? opt_frameState : null; + }; goog.inherits(ol.MapEvent, goog.events.Event); diff --git a/src/ol/object.js b/src/ol/object.js index e7942f8a0e..dbcf0a3e4e 100644 --- a/src/ol/object.js +++ b/src/ol/object.js @@ -6,6 +6,7 @@ */ goog.provide('ol.Object'); +goog.provide('ol.ObjectEventType'); goog.require('goog.array'); goog.require('goog.events'); @@ -13,6 +14,14 @@ goog.require('goog.events.EventTarget'); goog.require('goog.object'); +/** + * @enum {string} + */ +ol.ObjectEventType = { + CHANGED: 'changed' +}; + + /** * @enum {string} */ @@ -192,6 +201,7 @@ ol.Object.prototype.notify = function(key) { ol.Object.prototype.notifyInternal_ = function(key) { var eventType = ol.Object.getChangedEventType(key); this.dispatchEvent(eventType); + this.dispatchEvent(ol.ObjectEventType.CHANGED); }; diff --git a/src/ol/overlay/overlay.js b/src/ol/overlay/overlay.js index cc9f228feb..1c96bb3d5c 100644 --- a/src/ol/overlay/overlay.js +++ b/src/ol/overlay/overlay.js @@ -43,7 +43,13 @@ ol.overlay.Overlay = function(overlayOptions) { * @private * @type {Array.} */ - this.mapListenerKeys_ = []; + this.mapListenerKeys_ = null; + + /** + * @private + * @type {Array.} + */ + this.viewListenerKeys_ = null; if (goog.isDef(overlayOptions.coordinate)) { this.setCoordinate(overlayOptions.coordinate); @@ -60,6 +66,37 @@ ol.overlay.Overlay = function(overlayOptions) { }; +/** + * @private + */ +ol.overlay.Overlay.prototype.handleViewChanged_ = function() { + goog.asserts.assert(!goog.isNull(this.map_)); + if (!goog.isNull(this.viewListenerKeys_)) { + goog.array.forEach(this.viewListenerKeys_, goog.events.unlistenByKey); + this.viewListenerKeys_ = null; + } + var view = this.map_.getView(); + if (goog.isDefAndNotNull(view)) { + // FIXME works for View2D only + goog.asserts.assert(view instanceof ol.View2D); + this.viewListenerKeys_ = [ + goog.events.listen( + view, ol.Object.getChangedEventType(ol.View2DProperty.CENTER), + this.updatePixelPosition_, false, this), + + goog.events.listen( + view, ol.Object.getChangedEventType(ol.View2DProperty.RESOLUTION), + this.updatePixelPosition_, false, this), + + goog.events.listen( + view, ol.Object.getChangedEventType(ol.View2DProperty.ROTATION), + this.updatePixelPosition_, false, this) + ]; + this.updatePixelPosition_(); + } +}; + + /** * @param {ol.Coordinate} coordinate Coordinate for the overlay's position on * the map. @@ -100,25 +137,24 @@ ol.overlay.Overlay.prototype.getElement = function() { */ ol.overlay.Overlay.prototype.setMap = function(map) { this.map_ = map; - goog.array.forEach(this.mapListenerKeys_, goog.events.unlistenByKey); + if (!goog.isNull(this.mapListenerKeys_)) { + goog.array.forEach(this.mapListenerKeys_, goog.events.unlistenByKey); + this.mapListenerKeys_ = null; + } if (this.element_) { this.setElement(this.element_); } - this.mapListenerKeys_ = map ? [ - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.CENTER), - this.updatePixelPosition_, false, this), - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.RESOLUTION), - this.updatePixelPosition_, false, this), - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.ROTATION), - this.updatePixelPosition_, false, this), - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.SIZE), - this.updatePixelPosition_, false, this) - ] : []; - this.updatePixelPosition_(); + if (goog.isDefAndNotNull(map)) { + this.mapListenerKeys_ = [ + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.SIZE), + this.updatePixelPosition_, false, this), + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.VIEW), + this.handleViewChanged_, false, this) + ]; + this.handleViewChanged_(); + } }; diff --git a/src/ol/projection.js b/src/ol/projection.js index 6efc7242c9..3afd93e977 100644 --- a/src/ol/projection.js +++ b/src/ol/projection.js @@ -87,7 +87,7 @@ ol.Projection.prototype.getUnits = function() { */ ol.Proj4jsProjection = function(code, proj4jsProj) { - var units = /** @type {ol.ProjectionUnits} */ proj4jsProj.units; + var units = /** @type {ol.ProjectionUnits} */ (proj4jsProj.units); goog.base(this, code, units, null); diff --git a/src/ol/renderer/dom/domlayerrenderer.js b/src/ol/renderer/dom/domlayerrenderer.js index ed7ae1203f..d8ff224761 100644 --- a/src/ol/renderer/dom/domlayerrenderer.js +++ b/src/ol/renderer/dom/domlayerrenderer.js @@ -1,6 +1,9 @@ goog.provide('ol.renderer.dom.Layer'); goog.require('ol.Coordinate'); +goog.require('ol.FrameState'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.LayerState'); goog.require('ol.renderer.Layer'); @@ -13,6 +16,7 @@ goog.require('ol.renderer.Layer'); * @param {!Element} target Target. */ ol.renderer.dom.Layer = function(mapRenderer, layer, target) { + goog.base(this, mapRenderer, layer); /** @@ -21,27 +25,15 @@ ol.renderer.dom.Layer = function(mapRenderer, layer, target) { */ this.target = target; - /** - * Top left corner of the target in map coords. - * - * @type {ol.Coordinate} - * @protected - */ - this.origin = null; - - this.handleLayerOpacityChange(); - this.handleLayerVisibleChange(); - }; goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer); /** - * @inheritDoc - * @return {ol.renderer.Map} Map renderer. + * @return {!Element} Target. */ -ol.renderer.dom.Layer.prototype.getMapRenderer = function() { - return /** @type {ol.renderer.dom.Map} */ goog.base(this, 'getMapRenderer'); +ol.renderer.dom.Layer.prototype.getTarget = function() { + return this.target; }; @@ -57,7 +49,7 @@ ol.renderer.dom.Layer.prototype.handleLayerLoad = function() { * @inheritDoc */ ol.renderer.dom.Layer.prototype.handleLayerOpacityChange = function() { - goog.style.setOpacity(this.target, this.getLayer().getOpacity()); + this.getMap().render(); }; @@ -65,22 +57,12 @@ ol.renderer.dom.Layer.prototype.handleLayerOpacityChange = function() { * @inheritDoc */ ol.renderer.dom.Layer.prototype.handleLayerVisibleChange = function() { - goog.style.showElement(this.target, this.getLayer().getVisible()); + this.getMap().render(); }; /** - * Render. - * @param {number} time Time. + * @param {ol.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. */ ol.renderer.dom.Layer.prototype.renderFrame = goog.abstractMethod; - - -/** - * Set the location of the top left corner of the target. - * - * @param {ol.Coordinate} origin Origin. - */ -ol.renderer.dom.Layer.prototype.setOrigin = function(origin) { - this.origin = origin; -}; diff --git a/src/ol/renderer/dom/dommaprenderer.js b/src/ol/renderer/dom/dommaprenderer.js index 108f8a2cb9..befec3767c 100644 --- a/src/ol/renderer/dom/dommaprenderer.js +++ b/src/ol/renderer/dom/dommaprenderer.js @@ -5,8 +5,9 @@ goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.events.Event'); -goog.require('goog.functions'); +goog.require('goog.style'); goog.require('ol.Coordinate'); +goog.require('ol.FrameState'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.dom.TileLayer'); @@ -37,92 +38,32 @@ ol.renderer.dom.Map = function(container, map) { goog.dom.insertChildAt(container, this.layersPane_, 0); /** - * @type {Object} * @private + * @type {boolean} */ - this.layerPanes_ = {}; + this.renderedVisible_ = true; - /** - * @type {ol.Coordinate} - * @private - */ - this.renderedCenter_ = null; - - /** - * @type {number | undefined} - * @private - */ - this.renderedResolution_ = undefined; - - /** - * @type {ol.Size} - * @private - */ - this.renderedSize_ = null; - - /** - * @type {number | undefined} - * @private - */ - this.renderedRotation_ = undefined; - - /** - * The origin (top left) of the layers pane in map coordinates. - * - * @type {ol.Coordinate} - * @private - */ - this.layersPaneOrigin_ = null; }; goog.inherits(ol.renderer.dom.Map, ol.renderer.Map); -/** - * Apply the given transform to the layers pane. - * @param {number} dx Translation along the x-axis. - * @param {number} dy Translation along the y-axis. - * @param {number} rotation Rotation angle. - * @private - */ -ol.renderer.dom.Map.prototype.applyTransform_ = function(dx, dy, rotation) { - var transform = - 'translate(' + Math.round(dx) + 'px, ' + Math.round(dy) + 'px) ' + - 'rotate(' + rotation.toFixed(6) + 'rad) ' + - 'scale3d(1, 1, 1)'; - - var style = this.layersPane_.style; - style.WebkitTransform = transform; - style.MozTransform = transform; - style.OTransform = transform; - style.msTransform = transform; - style.transform = transform; -}; - - /** * @inheritDoc */ -ol.renderer.dom.Map.prototype.canRotate = goog.functions.TRUE; +ol.renderer.dom.Map.prototype.addLayer = function(layer) { + goog.base(this, 'addLayer', layer); + this.getMap().render(); +}; /** * @inheritDoc */ ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { - if (layer instanceof ol.layer.TileLayer) { - - var layerPane = goog.dom.createElement(goog.dom.TagName.DIV); - layerPane.className = 'ol-layer'; - layerPane.style.position = 'absolute'; - goog.dom.appendChild(this.layersPane_, layerPane); - - var layerRenderer = new ol.renderer.dom.TileLayer(this, layer, layerPane); - - this.layerPanes_[goog.getUid(layerRenderer)] = layerPane; - + var layerRenderer = new ol.renderer.dom.TileLayer(this, layer); + goog.dom.appendChild(this.layersPane_, layerRenderer.getTarget()); return layerRenderer; - } else { goog.asserts.assert(false); return null; @@ -133,8 +74,8 @@ ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { /** * @inheritDoc */ -ol.renderer.dom.Map.prototype.handleCenterChanged = function() { - goog.base(this, 'handleCenterChanged'); +ol.renderer.dom.Map.prototype.removeLayer = function(layer) { + goog.base(this, 'removeLayer', layer); this.getMap().render(); }; @@ -142,145 +83,30 @@ ol.renderer.dom.Map.prototype.handleCenterChanged = function() { /** * @inheritDoc */ -ol.renderer.dom.Map.prototype.handleResolutionChanged = function() { - goog.base(this, 'handleResolutionChanged'); - this.getMap().render(); -}; +ol.renderer.dom.Map.prototype.renderFrame = function(frameState) { - -/** - * @inheritDoc - */ -ol.renderer.dom.Map.prototype.handleRotationChanged = function() { - goog.base(this, 'handleRotationChanged'); - this.getMap().render(); -}; - - -/** - * @inheritDoc - */ -ol.renderer.dom.Map.prototype.handleSizeChanged = function() { - goog.base(this, 'handleSizeChanged'); - this.getMap().render(); -}; - - -/** - * Render the map. Sets up the layers pane on first render and adjusts its - * position as needed on subsequent calls. - * @inheritDoc - */ -ol.renderer.dom.Map.prototype.renderFrame = function(time) { - var map = this.getMap(); - if (!map.isDef()) { + if (goog.isNull(frameState)) { + if (this.renderedVisible_) { + goog.style.showElement(this.layersPane_, false); + this.renderedVisible_ = false; + } return; } - var mapCenter = map.getCenter(); - var mapSize = map.getSize(); - var mapResolution = map.getResolution(); - var mapRotation = map.getRotation(); - - goog.asserts.assert(goog.isDefAndNotNull(mapCenter)); - goog.asserts.assert(goog.isDef(mapResolution)); - goog.asserts.assert(goog.isDef(mapRotation)); - goog.asserts.assert(goog.isDefAndNotNull(mapSize)); - - if (goog.isNull(this.renderedCenter_)) { - // first rendering - goog.asserts.assert(!goog.isDef(this.renderedResolution_)); - goog.asserts.assert(!goog.isDef(this.renderedRotation_)); - goog.asserts.assert(goog.isNull(this.renderedSize_)); - this.resetLayersPane_(); - } else { - goog.asserts.assert(goog.isDef(this.renderedResolution_)); - goog.asserts.assert(!goog.isNull(this.renderedSize_)); - if (mapResolution !== this.renderedResolution_ || - !mapSize.equals(this.renderedSize_)) { - // resolution or size changed, adjust layers pane - this.resetLayersPane_(); - } else if (!mapCenter.equals(this.renderedCenter_) || - mapRotation !== this.renderedRotation_) { - // same resolution and size, new center or rotation - this.transformLayersPane_(); + goog.array.forEach(frameState.layersArray, function(layer) { + var layerState = frameState.layerStates[goog.getUid(layer)]; + if (!layerState.ready) { + return; } + var layerRenderer = this.getLayerRenderer(layer); + layerRenderer.renderFrame(frameState, layerState); + }, this); + + if (!this.renderedVisible_) { + goog.style.showElement(this.layersPane_, true); + this.renderedVisible_ = true; } - this.renderedCenter_ = mapCenter; - this.renderedResolution_ = mapResolution; - this.renderedRotation_ = mapRotation; - this.renderedSize_ = mapSize; - - var requestRenderFrame = false; - this.forEachReadyVisibleLayer(function(layer, layerRenderer) { - if (layerRenderer.renderFrame(time)) { - requestRenderFrame = true; - } - }); - - if (requestRenderFrame) { - map.requestRenderFrame(); - } + this.calculateMatrices2D(frameState); }; - - -/** - * Reset the layers pane to its initial position. - * @private - */ -ol.renderer.dom.Map.prototype.resetLayersPane_ = function() { - var map = this.map; - var mapSize = map.getSize(); - var halfWidth = mapSize.width / 2; - var halfHeight = mapSize.height / 2; - var center = map.getCenter(); - var resolution = map.getResolution(); - var origin = new ol.Coordinate( - center.x - resolution * halfWidth, - center.y + resolution * halfHeight); - this.layersPaneOrigin_ = origin; - this.setTransformOrigin_(halfWidth, halfHeight); - this.applyTransform_(0, 0, map.getRotation()); - goog.object.forEach(this.layerRenderers, function(layerRenderer) { - layerRenderer.setOrigin(origin); - }); -}; - - -/** - * Set the transform-origin CSS property of the layers pane. - * @param {number} x The x-axis origin. - * @param {number} y The y-axis origin. - * @private - */ -ol.renderer.dom.Map.prototype.setTransformOrigin_ = function(x, y) { - var origin = Math.round(x) + 'px ' + Math.round(y) + 'px'; - var style = this.layersPane_.style; - style.WebkitTransformOrigin = origin; - style.MozTransformOrigin = origin; - style.OTransformOrigin = origin; - style.msTransformOrigin = origin; - style.transformOrigin = origin; - -}; - - -/** - * Apply the appropriate transform to the layers pane. - * @private - */ -ol.renderer.dom.Map.prototype.transformLayersPane_ = function() { - var map = this.map; - var resolution = map.getResolution(); - var center = map.getCenter(); - var size = map.getSize(); - var origin = this.layersPaneOrigin_; - var ox = (center.x - origin.x) / resolution; - var oy = (origin.y - center.y) / resolution; - this.setTransformOrigin_(ox, oy); - var dx = ox - (size.width / 2); - var dy = oy - (size.height / 2); - this.applyTransform_(-dx, -dy, map.getRotation()); -}; diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index e753a6020c..cdbfb92305 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -1,13 +1,23 @@ +// FIXME probably need to reset TileLayerZ if offsets get too large +// FIXME when zooming out, preserve higher Z divs to avoid white flash + goog.provide('ol.renderer.dom.TileLayer'); goog.require('goog.asserts'); goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventType'); +goog.require('goog.math.Vec2'); +goog.require('goog.style'); +goog.require('goog.vec.Mat4'); goog.require('ol.Coordinate'); goog.require('ol.Extent'); +goog.require('ol.Size'); +goog.require('ol.TileCoord'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); goog.require('ol.renderer.dom.Layer'); +goog.require('ol.tilegrid.TileGrid'); @@ -16,212 +26,363 @@ goog.require('ol.renderer.dom.Layer'); * @extends {ol.renderer.dom.Layer} * @param {ol.renderer.Map} mapRenderer Map renderer. * @param {ol.layer.TileLayer} tileLayer Tile layer. - * @param {!Element} target Target. */ -ol.renderer.dom.TileLayer = function(mapRenderer, tileLayer, target) { +ol.renderer.dom.TileLayer = function(mapRenderer, tileLayer) { + + var target = goog.dom.createElement(goog.dom.TagName.DIV); + target.className = 'ol-layer'; + target.style.position = 'absolute'; + goog.base(this, mapRenderer, tileLayer, target); /** - * @type {Object} * @private + * @type {boolean} */ - this.renderedTiles_ = {}; + this.renderedVisible_ = true; /** - * @type {number|undefined} * @private + * @type {number} */ - this.renderedMapResolution_ = undefined; + this.renderedOpacity_ = 1; + + /** + * @private + * @type {Object.} + */ + this.tileLayerZs_ = {}; }; goog.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer); +/** + * @return {ol.layer.TileLayer} Tile layer. + */ +ol.renderer.dom.TileLayer.prototype.getTileLayer = function() { + return /** @type {ol.layer.TileLayer} */ (this.getLayer()); +}; + + /** * @inheritDoc - * @return {ol.layer.TileLayer} Layer. */ -ol.renderer.dom.TileLayer.prototype.getLayer = function() { - return /** @type {ol.layer.TileLayer} */ goog.base(this, 'getLayer'); -}; +ol.renderer.dom.TileLayer.prototype.renderFrame = + function(frameState, layerState) { - -/** - * Get the pixel offset between the tile origin and the container origin. - * @private - * @param {number} z Z. - * @param {number} resolution Resolution. - * @return {ol.Coordinate} Offset. - */ -ol.renderer.dom.TileLayer.prototype.getTileOffset_ = function(z, resolution) { - var tileLayer = this.getLayer(); - var tileSource = tileLayer.getTileSource(); - var tileGrid = tileSource.getTileGrid(); - var tileOrigin = tileGrid.getOrigin(z); - var offset = new ol.Coordinate( - Math.round((this.origin.x - tileOrigin.x) / resolution), - Math.round((tileOrigin.y - this.origin.y) / resolution)); - return offset; -}; - - -/** - * Get rid of all tiles that weren't drawn in the most recent rendering. - * @param {Object.>} tilesDrawnByZ Tiles just - * rendered. - * @private - */ -ol.renderer.dom.TileLayer.prototype.removeExtraTiles_ = - function(tilesDrawnByZ) { - var key, tileCoord, tilesDrawn, tile; - for (key in this.renderedTiles_) { - tileCoord = ol.TileCoord.createFromString(key); - tilesDrawn = tilesDrawnByZ[tileCoord.z]; - if (!(tilesDrawn && key in tilesDrawn)) { - tile = this.renderedTiles_[key]; - delete this.renderedTiles_[key]; - goog.dom.removeNode(tile.getImage(this)); + if (!layerState.visible) { + if (this.renderedVisible_) { + goog.style.showElement(this.target, false); + this.renderedVisible_ = false; } - } -}; - - -/** - * @inheritDoc - */ -ol.renderer.dom.TileLayer.prototype.renderFrame = function(time) { - - var map = this.getMap(); - if (!map.isDef()) { return; } - var mapExtent = /** @type {!ol.Extent} */ map.getRotatedExtent(); - var mapResolution = /** @type {number} */ map.getResolution(); - var resolutionChanged = (mapResolution !== this.renderedMapResolution_); - var tileLayer = this.getLayer(); + var view2DState = frameState.view2DState; + + var tileLayer = this.getTileLayer(); var tileSource = tileLayer.getTileSource(); var tileGrid = tileSource.getTileGrid(); + var z = tileGrid.getZForResolution(view2DState.resolution); + var tileResolution = tileGrid.getResolution(z); + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + frameState.extent, tileResolution); - // z represents the "best" resolution - var z = tileGrid.getZForResolution(mapResolution); - - /** - * @type {Object.>} - */ + /** @type {Object.>} */ var tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; - var tileRange = - tileGrid.getTileRangeForExtentAndResolution(mapExtent, mapResolution); + var findInterimTiles = function(z, tileRange) { + // FIXME this could be more efficient about filling partial holes + var fullyCovered = true; + var tile, tileCoord, tileCoordKey, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoord = new ol.TileCoord(z, x, y); + tileCoordKey = tileCoord.toString(); + if (tilesToDrawByZ[z] && tilesToDrawByZ[z][tileCoordKey]) { + return; + } + tile = tileSource.getTile(tileCoord); + if (!goog.isNull(tile) && tile.getState() == ol.TileState.LOADED) { + if (!tilesToDrawByZ[z]) { + tilesToDrawByZ[z] = {}; + } + tilesToDrawByZ[z][tileCoordKey] = tile; + } else { + fullyCovered = false; + } + } + } + return fullyCovered; + }; var allTilesLoaded = true; + var tile, tileCenter, tileCoord, tileState, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + + tileCoord = new ol.TileCoord(z, x, y); + tile = tileSource.getTile(tileCoord); + if (goog.isNull(tile)) { + continue; + } + + tileState = tile.getState(); + if (tileState == ol.TileState.IDLE) { + tileCenter = tileGrid.getTileCoordCenter(tileCoord); + frameState.tileQueue.enqueue(tile, tileCenter, tileResolution); + } else if (tileState == ol.TileState.LOADED) { + tilesToDrawByZ[z][tileCoord.toString()] = tile; + continue; + } else if (tileState == ol.TileState.ERROR) { + continue; + } + + allTilesLoaded = false; + tileGrid.forEachTileCoordParentTileRange(tileCoord, findInterimTiles); - // first pass through the tile range to determine all the tiles needed - tileRange.forEachTileCoord(z, function(tileCoord) { - var tile = tileSource.getTile(tileCoord); - if (goog.isNull(tile)) { - // we're outside the source's extent, continue - return; } - var key = tile.tileCoord.toString(); - var state = tile.getState(); - if (state == ol.TileState.IDLE) { - tile.load(); - } else if (state == ol.TileState.LOADED) { - tilesToDrawByZ[z][key] = tile; - return; - } - - allTilesLoaded = false; - - /** - * Look for already loaded tiles at alternate z that can serve as - * placeholders until tiles at the current z have loaded. - * - * TODO: make this more efficent for filling partial holes - */ - tileGrid.forEachTileCoordParentTileRange( - tileCoord, - function(altZ, altTileRange) { - var fullyCovered = true; - altTileRange.forEachTileCoord(altZ, function(altTileCoord) { - var tileKey = altTileCoord.toString(); - if (tilesToDrawByZ[altZ] && tilesToDrawByZ[altZ][tileKey]) { - return; - } - var altTile = tileSource.getTile(altTileCoord); - if (!goog.isNull(altTile) && - altTile.getState() == ol.TileState.LOADED) { - if (!(altZ in tilesToDrawByZ)) { - tilesToDrawByZ[altZ] = {}; - } - tilesToDrawByZ[altZ][tileKey] = altTile; - } else { - fullyCovered = false; - } - }); - return fullyCovered; - }); - - }, this); + } /** @type {Array.} */ var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); goog.array.sort(zs); - var fragment = document.createDocumentFragment(); - var altFragment = document.createDocumentFragment(); - var newTiles = false; - var newAltTiles = false; - for (var i = 0, ii = zs.length; i < ii; ++i) { - var tileZ = zs[i]; - var tilesToDraw = tilesToDrawByZ[tileZ]; - var tileOffset = this.getTileOffset_(tileZ, mapResolution); - for (var key in tilesToDraw) { - var tile = tilesToDraw[key]; - var tileCoord = tile.tileCoord; - var pixelBounds = tileGrid.getPixelBoundsForTileCoordAndResolution( - tileCoord, mapResolution); - var img = tile.getImage(this); - var style = img.style; - var append = !(key in this.renderedTiles_); - if (append || resolutionChanged) { - style.left = (pixelBounds.minX - tileOffset.x) + 'px'; - style.top = (-pixelBounds.maxY - tileOffset.y) + 'px'; - style.width = pixelBounds.getWidth() + 'px'; - style.height = pixelBounds.getHeight() + 'px'; - } - if (append) { - this.renderedTiles_[key] = tile; - style.position = 'absolute'; - if (tileZ === z) { - goog.dom.appendChild(fragment, img); - newTiles = true; - } else { - goog.dom.appendChild(altFragment, img); - newAltTiles = true; + /** @type {Object.} */ + var newTileLayerZKeys = {}; + + var tileSize = tileGrid.getTileSize(); + var iz, tileCoordKey, tileCoordOrigin, tileLayerZ, tileLayerZKey, tilesToDraw; + for (iz = 0; iz < zs.length; ++iz) { + tileLayerZKey = zs[iz]; + if (tileLayerZKey in this.tileLayerZs_) { + tileLayerZ = this.tileLayerZs_[tileLayerZKey]; + } else { + tileCoordOrigin = + tileGrid.getTileCoordForCoordAndZ(view2DState.center, tileLayerZKey); + tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin); + newTileLayerZKeys[tileLayerZKey] = true; + this.tileLayerZs_[tileLayerZKey] = tileLayerZ; + } + tilesToDraw = tilesToDrawByZ[tileLayerZKey]; + for (tileCoordKey in tilesToDraw) { + tileLayerZ.addTile(tilesToDraw[tileCoordKey]); + } + tileLayerZ.finalizeAddTiles(); + } + + /** @type {Array.} */ + var tileLayerZKeys = + goog.array.map(goog.object.getKeys(this.tileLayerZs_), Number); + goog.array.sort(tileLayerZKeys); + + var i, j, origin, resolution; + var transform = goog.vec.Mat4.createNumber(); + for (i = 0; i < tileLayerZKeys.length; ++i) { + tileLayerZKey = tileLayerZKeys[i]; + tileLayerZ = this.tileLayerZs_[tileLayerZKey]; + if (!(tileLayerZKey in tilesToDrawByZ)) { + goog.dom.removeNode(tileLayerZ.target); + delete this.tileLayerZs_[tileLayerZKey]; + continue; + } + resolution = tileLayerZ.getResolution(); + origin = tileLayerZ.getOrigin(); + goog.vec.Mat4.makeIdentity(transform); + goog.vec.Mat4.translate( + transform, frameState.size.width / 2, frameState.size.height / 2, 0); + goog.vec.Mat4.rotateZ(transform, view2DState.rotation); + goog.vec.Mat4.scale(transform, resolution / view2DState.resolution, + resolution / view2DState.resolution, 1); + goog.vec.Mat4.translate( + transform, + (origin.x - view2DState.center.x) / resolution, + (view2DState.center.y - origin.y) / resolution, + 0); + tileLayerZ.setTransform(transform); + if (tileLayerZKey in newTileLayerZKeys) { + for (j = tileLayerZKey - 1; j >= 0; --j) { + if (j in this.tileLayerZs_) { + goog.dom.insertSiblingAfter( + tileLayerZ.target, this.tileLayerZs_[j].target); + break; } } - } - } - - if (newAltTiles) { - var child = this.target.firstChild; - if (child) { - goog.dom.insertSiblingBefore(altFragment, child); + if (j < 0) { + goog.dom.insertChildAt(this.target, tileLayerZ.target, 0); + } } else { - goog.dom.appendChild(this.target, altFragment); + if (!frameState.viewHints[ol.ViewHint.ANIMATING] && + !frameState.viewHints[ol.ViewHint.PANNING]) { + tileLayerZ.removeTilesOutsideExtent(frameState.extent); + } } } - if (newTiles) { - goog.dom.appendChild(this.target, fragment); + + if (layerState.opacity != this.renderedOpacity_) { + goog.style.setOpacity(this.target, layerState.opacity); + this.renderedOpacity_ = layerState.opacity; } - this.renderedMapResolution_ = mapResolution; + if (layerState.visible && !this.renderedVisible_) { + goog.style.showElement(this.target, true); + this.renderedVisible_ = true; + } - this.removeExtraTiles_(tilesToDrawByZ); + if (!allTilesLoaded) { + frameState.animate = true; + } - return !allTilesLoaded; +}; + + + +/** + * @constructor + * @private + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.TileCoord} tileCoordOrigin Tile coord origin. + */ +ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) { + + /** + * @type {!Element} + */ + this.target = goog.dom.createElement(goog.dom.TagName.DIV); + this.target.style.position = 'absolute'; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.tileGrid_ = tileGrid; + + /** + * @private + * @type {ol.TileCoord} + */ + this.tileCoordOrigin_ = tileCoordOrigin; + + /** + * @private + * @type {ol.Coordinate} + */ + this.origin_ = tileGrid.getTileCoordExtent(tileCoordOrigin).getTopLeft(); + + /** + * @private + * @type {number} + */ + this.resolution_ = tileGrid.getResolution(tileCoordOrigin.z); + + /** + * @private + * @type {Object.} + */ + this.tiles_ = {}; + + /** + * @private + * @type {DocumentFragment} + */ + this.documentFragment_ = null; + + /** + * @private + * @type {goog.vec.Mat4.AnyType} + */ + this.transform_ = goog.vec.Mat4.createNumberIdentity(); + +}; + + +/** + * @param {ol.Tile} tile Tile. + */ +ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile) { + var tileCoord = tile.tileCoord; + goog.asserts.assert(tileCoord.z == this.tileCoordOrigin_.z); + var tileCoordKey = tileCoord.toString(); + if (tileCoordKey in this.tiles_) { + return; + } + var tileSize = this.tileGrid_.getTileSize(); + var image = tile.getImage(this); + var style = image.style; + style.position = 'absolute'; + style.left = + ((tileCoord.x - this.tileCoordOrigin_.x) * tileSize.width) + 'px'; + style.top = + ((this.tileCoordOrigin_.y - tileCoord.y) * tileSize.height) + 'px'; + if (goog.isNull(this.documentFragment_)) { + this.documentFragment_ = document.createDocumentFragment(); + } + goog.dom.appendChild(this.documentFragment_, image); + this.tiles_[tileCoordKey] = tile; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() { + if (!goog.isNull(this.documentFragment_)) { + goog.dom.appendChild(this.target, this.documentFragment_); + this.documentFragment_ = null; + } +}; + + +/** + * @return {ol.Coordinate} Origin. + */ +ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() { + return this.origin_; +}; + + +/** + * @return {number} Resolution. + */ +ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() { + return this.resolution_; +}; + + +/** + * @param {ol.Extent} extent Extent. + */ +ol.renderer.dom.TileLayerZ_.prototype.removeTilesOutsideExtent = + function(extent) { + var tileRange = + this.tileGrid_.getTileRangeForExtentAndZ(extent, this.tileCoordOrigin_.z); + var tilesToRemove = []; + var tile, tileCoordKey; + for (tileCoordKey in this.tiles_) { + tile = this.tiles_[tileCoordKey]; + if (!tileRange.contains(tile.tileCoord)) { + tilesToRemove.push(tile); + } + } + var i; + for (i = 0; i < tilesToRemove.length; ++i) { + tile = tilesToRemove[i]; + tileCoordKey = tile.tileCoord.toString(); + goog.dom.removeNode(tile.getImage(this)); + delete this.tiles_[tileCoordKey]; + } +}; + + +/** + * @param {goog.vec.Mat4.AnyType} transform Transform. + */ +ol.renderer.dom.TileLayerZ_.prototype.setTransform = function(transform) { + if (!goog.vec.Mat4.equals(transform, this.transform_)) { + ol.dom.transformElement2D(this.target, transform, 6); + goog.vec.Mat4.setFromArray(this.transform_, transform); + } }; diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index b1caf6a48d..14b3db6d9f 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -1,11 +1,15 @@ goog.provide('ol.renderer.Map'); goog.require('goog.Disposable'); +goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.functions'); goog.require('goog.fx.anim'); goog.require('goog.fx.anim.Animated'); goog.require('goog.vec.Mat4'); +goog.require('ol.FrameState'); +goog.require('ol.View2D'); +goog.require('ol.View2DProperty'); @@ -43,23 +47,12 @@ ol.renderer.Map = function(container, map) { */ this.layersListenerKeys_ = null; - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.coordinateToPixelMatrix_ = goog.vec.Mat4.createNumber(); /** * @private - * @type {goog.vec.Mat4.Number} + * @type {?number} */ - this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber(); - - /** - * @private - * @type {boolean} - */ - this.matricesDirty_ = true; + this.viewPropertyListenerKey_ = null; /** * @private @@ -70,25 +63,17 @@ ol.renderer.Map = function(container, map) { map, ol.Object.getChangedEventType(ol.MapProperty.BACKGROUND_COLOR), this.handleBackgroundColorChanged, false, this), - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.CENTER), - this.handleCenterChanged, false, this), - goog.events.listen( map, ol.Object.getChangedEventType(ol.MapProperty.LAYERS), this.handleLayersChanged, false, this), - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.RESOLUTION), - this.handleResolutionChanged, false, this), - - goog.events.listen( - map, ol.Object.getChangedEventType(ol.MapProperty.ROTATION), - this.handleRotationChanged, false, this), - goog.events.listen( map, ol.Object.getChangedEventType(ol.MapProperty.SIZE), - this.handleSizeChanged, false, this) + this.handleSizeChanged, false, this), + + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.VIEW), + this.handleViewChanged, false, this) ]; }; @@ -106,9 +91,35 @@ ol.renderer.Map.prototype.addLayer = function(layer) { /** - * @return {boolean} Can rotate. + * @param {ol.FrameState} frameState FrameState. + * @protected */ -ol.renderer.Map.prototype.canRotate = goog.functions.FALSE; +ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) { + + var view2DState = frameState.view2DState; + var coordinateToPixelMatrix = frameState.coordinateToPixelMatrix; + + goog.vec.Mat4.makeIdentity(coordinateToPixelMatrix); + goog.vec.Mat4.translate(coordinateToPixelMatrix, + frameState.size.width / 2, + frameState.size.height / 2, + 0); + goog.vec.Mat4.scale(coordinateToPixelMatrix, + 1 / view2DState.resolution, + -1 / view2DState.resolution, + 1); + goog.vec.Mat4.rotateZ(coordinateToPixelMatrix, + -view2DState.rotation); + goog.vec.Mat4.translate(coordinateToPixelMatrix, + -view2DState.center.x, + -view2DState.center.y, + 0); + + var inverted = goog.vec.Mat4.invert( + coordinateToPixelMatrix, frameState.pixelToCoordinateMatrix); + goog.asserts.assert(inverted); + +}; /** @@ -127,6 +138,9 @@ ol.renderer.Map.prototype.disposeInternal = function() { goog.dispose(layerRenderer); }); goog.array.forEach(this.mapListenerKeys_, goog.events.unlistenByKey); + if (!goog.isNull(this.viewPropertyListenerKey_)) { + goog.events.unlistenByKey(this.viewPropertyListenerKey_); + } if (!goog.isNull(this.layersListenerKeys_)) { goog.array.forEach(this.layersListenerKeys_, goog.events.unlistenByKey); } @@ -134,37 +148,6 @@ ol.renderer.Map.prototype.disposeInternal = function() { }; -/** - * @param {function(this: T, ol.layer.Layer, ol.renderer.Layer, number)} f - * Function. - * @param {T=} opt_obj Object. - * @template T - */ -ol.renderer.Map.prototype.forEachReadyVisibleLayer = function(f, opt_obj) { - var layers = this.map.getLayers(); - if (goog.isDef(layers)) { - layers.forEach(function(layer, index) { - if (layer.isReady() && layer.getVisible()) { - var layerRenderer = this.getLayerRenderer(layer); - f.call(opt_obj, layer, layerRenderer, index); - } - }, this); - } -}; - - -/** - * @param {ol.Pixel} pixel Pixel. - * @return {ol.Coordinate} Coordinate. - */ -ol.renderer.Map.prototype.getCoordinateFromPixel = function(pixel) { - this.updateMatrices_(); - var vec3 = [pixel.x, pixel.y, 0]; - goog.vec.Mat4.multVec3(this.pixelToCoordinateMatrix_, vec3, vec3); - return new ol.Coordinate(vec3[0], vec3[1]); -}; - - /** * @param {ol.layer.Layer} layer Layer. * @protected @@ -186,38 +169,18 @@ ol.renderer.Map.prototype.getMap = function() { }; -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @return {ol.Pixel} Pixel. - */ -ol.renderer.Map.prototype.getPixelFromCoordinate = function(coordinate) { - this.updateMatrices_(); - var vec3 = [coordinate.x, coordinate.y, 0]; - goog.vec.Mat4.multVec3(this.coordinateToPixelMatrix_, vec3, vec3); - return new ol.Pixel(vec3[0], vec3[1]); -}; - - /** * Handle background color changed. */ ol.renderer.Map.prototype.handleBackgroundColorChanged = goog.nullFunction; -/** - * @protected - */ -ol.renderer.Map.prototype.handleCenterChanged = function() { - this.matricesDirty_ = true; -}; - - /** * @param {ol.CollectionEvent} collectionEvent Collection event. * @protected */ ol.renderer.Map.prototype.handleLayersAdd = function(collectionEvent) { - var layer = /** @type {ol.layer.Layer} */ collectionEvent.elem; + var layer = /** @type {ol.layer.Layer} */ (collectionEvent.elem); this.addLayer(layer); }; @@ -226,10 +189,7 @@ ol.renderer.Map.prototype.handleLayersAdd = function(collectionEvent) { * @protected */ ol.renderer.Map.prototype.handleLayersChanged = function() { - var layerRenderers = goog.object.getValues(this.layerRenderers); - goog.array.forEach(layerRenderers, function(layerRenderer) { - this.removeLayerRenderer(layerRenderer); - }, this); + goog.disposeAll(goog.object.getValues(this.layerRenderers)); this.layerRenderers = {}; if (!goog.isNull(this.layersListenerKeys_)) { goog.array.forEach(this.layersListenerKeys_, goog.events.unlistenByKey); @@ -253,7 +213,7 @@ ol.renderer.Map.prototype.handleLayersChanged = function() { * @protected */ ol.renderer.Map.prototype.handleLayersRemove = function(collectionEvent) { - var layer = /** @type {ol.layer.Layer} */ collectionEvent.elem; + var layer = /** @type {ol.layer.Layer} */ (collectionEvent.elem); this.removeLayer(layer); }; @@ -261,16 +221,8 @@ ol.renderer.Map.prototype.handleLayersRemove = function(collectionEvent) { /** * @protected */ -ol.renderer.Map.prototype.handleResolutionChanged = function() { - this.matricesDirty_ = true; -}; - - -/** - * @protected - */ -ol.renderer.Map.prototype.handleRotationChanged = function() { - this.matricesDirty_ = true; +ol.renderer.Map.prototype.handleViewPropertyChanged = function() { + this.getMap().render(); }; @@ -278,7 +230,25 @@ ol.renderer.Map.prototype.handleRotationChanged = function() { * @protected */ ol.renderer.Map.prototype.handleSizeChanged = function() { - this.matricesDirty_ = true; + this.getMap().render(); +}; + + +/** + * @protected + */ +ol.renderer.Map.prototype.handleViewChanged = function() { + if (!goog.isNull(this.viewPropertyListenerKey_)) { + goog.events.unlistenByKey(this.viewPropertyListenerKey_); + this.viewPropertyListenerKey_ = null; + } + var view = this.getMap().getView(); + if (goog.isDefAndNotNull(view)) { + this.viewPropertyListenerKey_ = goog.events.listen( + view, ol.ObjectEventType.CHANGED, + this.handleViewPropertyChanged, false, this); + } + this.getMap().render(); }; @@ -310,9 +280,9 @@ ol.renderer.Map.prototype.removeLayerRenderer = function(layer) { /** * Render. - * @param {number} time Time. + * @param {?ol.FrameState} frameState Frame state. */ -ol.renderer.Map.prototype.renderFrame = goog.functions.FALSE; +ol.renderer.Map.prototype.renderFrame = goog.nullFunction; /** @@ -325,44 +295,3 @@ ol.renderer.Map.prototype.setLayerRenderer = function(layer, layerRenderer) { goog.asserts.assert(!(key in this.layerRenderers)); this.layerRenderers[key] = layerRenderer; }; - - -/** - * @private - */ -ol.renderer.Map.prototype.updateMatrices_ = function() { - - if (this.matricesDirty_) { - - var map = this.map; - var center = /** @type {!ol.Coordinate} */ map.getCenter(); - var resolution = /** @type {number} */ map.getResolution(); - var rotation = map.getRotation(); - var size = /** @type {!ol.Size} */ map.getSize(); - - goog.vec.Mat4.makeIdentity(this.coordinateToPixelMatrix_); - goog.vec.Mat4.translate(this.coordinateToPixelMatrix_, - size.width / 2, - size.height / 2, - 0); - goog.vec.Mat4.scale(this.coordinateToPixelMatrix_, - 1 / resolution, - -1 / resolution, - 1); - if (this.canRotate() && goog.isDef(rotation)) { - goog.vec.Mat4.rotateZ(this.coordinateToPixelMatrix_, -rotation); - } - goog.vec.Mat4.translate(this.coordinateToPixelMatrix_, - -center.x, - -center.y, - 0); - - var inverted = goog.vec.Mat4.invert( - this.coordinateToPixelMatrix_, this.pixelToCoordinateMatrix_); - goog.asserts.assert(inverted); - - this.matricesDirty_ = false; - - } - -}; diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js index 465d6ac55a..7fce8f5256 100644 --- a/src/ol/renderer/webgl/webgllayerrenderer.js +++ b/src/ol/renderer/webgl/webgllayerrenderer.js @@ -1,8 +1,13 @@ +// FIXME move colorMatrix_ elsewhere? + goog.provide('ol.renderer.webgl.Layer'); goog.require('goog.vec.Mat4'); +goog.require('ol.FrameState'); goog.require('ol.layer.Layer'); +goog.require('ol.layer.LayerState'); goog.require('ol.renderer.Layer'); +goog.require('ol.vec.Mat4'); @@ -13,7 +18,50 @@ goog.require('ol.renderer.Layer'); * @param {ol.layer.Layer} layer Layer. */ ol.renderer.webgl.Layer = function(mapRenderer, layer) { + goog.base(this, mapRenderer, layer); + + /** + * @private + * @type {!goog.vec.Mat4.Float32} + */ + this.brightnessMatrix_ = goog.vec.Mat4.createFloat32(); + + /** + * @private + * @type {!goog.vec.Mat4.Float32} + */ + this.contrastMatrix_ = goog.vec.Mat4.createFloat32(); + + /** + * @private + * @type {!goog.vec.Mat4.Float32} + */ + this.hueMatrix_ = goog.vec.Mat4.createFloat32(); + + /** + * @private + * @type {!goog.vec.Mat4.Float32} + */ + this.saturationMatrix_ = goog.vec.Mat4.createFloat32(); + + /** + * @private + * @type {!goog.vec.Mat4.Float32} + */ + this.colorMatrix_ = goog.vec.Mat4.createFloat32(); + + /** + * @private + * @type {boolean} + */ + this.colorMatrixDirty_ = true; + + this.handleLayerBrightnessChange(); + this.handleLayerContrastChange(); + this.handleLayerHueChange(); + this.handleLayerSaturationChange(); + }; goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); @@ -27,17 +75,28 @@ ol.renderer.webgl.Layer.prototype.dispatchChangeEvent = function() { /** - * @inheritDoc - * @return {ol.renderer.Map} MapRenderer. + * @return {!goog.vec.Mat4.Float32} Color matrix. */ -ol.renderer.webgl.Layer.prototype.getMapRenderer = function() { - return /** @type {ol.renderer.webgl.Map} */ goog.base( - this, 'getMapRenderer'); +ol.renderer.webgl.Layer.prototype.getColorMatrix = function() { + if (this.colorMatrixDirty_) { + this.updateColorMatrix_(); + } + return this.colorMatrix_; }; /** - * @return {goog.vec.Mat4.AnyType} Matrix. + * @inheritDoc + * @return {ol.renderer.Map} MapRenderer. + */ +ol.renderer.webgl.Layer.prototype.getMapRenderer = function() { + return /** @type {ol.renderer.webgl.Map} */ (goog.base( + this, 'getMapRenderer')); +}; + + +/** + * @return {!goog.vec.Mat4.Number} Matrix. */ ol.renderer.webgl.Layer.prototype.getMatrix = goog.abstractMethod; @@ -52,6 +111,9 @@ ol.renderer.webgl.Layer.prototype.getTexture = goog.abstractMethod; * @inheritDoc */ ol.renderer.webgl.Layer.prototype.handleLayerBrightnessChange = function() { + var value = this.getLayer().getBrightness(); + ol.vec.Mat4.makeBrightness(this.brightnessMatrix_, value); + this.colorMatrixDirty_ = true; this.dispatchChangeEvent(); }; @@ -60,6 +122,9 @@ ol.renderer.webgl.Layer.prototype.handleLayerBrightnessChange = function() { * @inheritDoc */ ol.renderer.webgl.Layer.prototype.handleLayerContrastChange = function() { + var value = this.getLayer().getContrast(); + ol.vec.Mat4.makeContrast(this.contrastMatrix_, value); + this.colorMatrixDirty_ = true; this.dispatchChangeEvent(); }; @@ -68,6 +133,9 @@ ol.renderer.webgl.Layer.prototype.handleLayerContrastChange = function() { * @inheritDoc */ ol.renderer.webgl.Layer.prototype.handleLayerHueChange = function() { + var value = this.getLayer().getHue(); + ol.vec.Mat4.makeHue(this.hueMatrix_, value); + this.colorMatrixDirty_ = true; this.dispatchChangeEvent(); }; @@ -92,6 +160,9 @@ ol.renderer.webgl.Layer.prototype.handleLayerOpacityChange = function() { * @inheritDoc */ ol.renderer.webgl.Layer.prototype.handleLayerSaturationChange = function() { + var saturation = this.getLayer().getSaturation(); + ol.vec.Mat4.makeSaturation(this.saturationMatrix_, saturation); + this.colorMatrixDirty_ = true; this.dispatchChangeEvent(); }; @@ -112,7 +183,21 @@ ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = goog.nullFunction; /** * Render. - * @param {number} time Time. - * @return {boolean} Request render frame. + * @param {ol.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. */ ol.renderer.webgl.Layer.prototype.renderFrame = goog.abstractMethod; + + +/** + * @private + */ +ol.renderer.webgl.Layer.prototype.updateColorMatrix_ = function() { + var colorMatrix = this.colorMatrix_; + goog.vec.Mat4.makeIdentity(colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, this.contrastMatrix_, colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, this.brightnessMatrix_, colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, this.saturationMatrix_, colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, this.hueMatrix_, colorMatrix); + this.colorMatrixDirty_ = false; +}; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 5a7d893bfb..3a071a686e 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -1,7 +1,5 @@ // FIXME clear textureCache -// FIXME defer texture loads until after render when animating // FIXME generational tile texture garbage collector newFrame/get -// FIXME defer cleanup until post-render // FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE) goog.provide('ol.renderer.webgl.Map'); @@ -14,10 +12,9 @@ goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.events.Event'); goog.require('goog.events.EventType'); -goog.require('goog.functions'); goog.require('goog.style'); -goog.require('goog.vec.Mat4'); goog.require('goog.webgl'); +goog.require('ol.Tile'); goog.require('ol.layer.Layer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.webgl.FragmentShader'); @@ -119,6 +116,12 @@ ol.renderer.webgl.Map = function(container, map) { this.canvas_.className = 'ol-unselectable'; goog.dom.insertChildAt(container, this.canvas_, 0); + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + /** * @private * @type {ol.Size} @@ -143,12 +146,6 @@ ol.renderer.webgl.Map = function(container, map) { goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED, this.handleWebGLContextResourced, false, this); - /** - * @private - * @type {ol.Color} - */ - this.clearColor_ = new ol.Color(1, 1, 1, 1); - /** * @private * @type {{aPosition: number, @@ -220,15 +217,15 @@ ol.renderer.webgl.Map.prototype.addLayer = function(layer) { /** - * @param {Image} image Image. + * @param {ol.Tile} tile Tile. * @param {number} magFilter Mag filter. * @param {number} minFilter Min filter. */ -ol.renderer.webgl.Map.prototype.bindImageTexture = - function(image, magFilter, minFilter) { +ol.renderer.webgl.Map.prototype.bindTileTexture = + function(tile, magFilter, minFilter) { var gl = this.getGL(); - var imageKey = image.src; - var textureCacheEntry = this.textureCache_[imageKey]; + var tileKey = tile.getKey(); + var textureCacheEntry = this.textureCache_[tileKey]; if (goog.isDef(textureCacheEntry)) { gl.bindTexture(goog.webgl.TEXTURE_2D, textureCacheEntry.texture); if (textureCacheEntry.magFilter != magFilter) { @@ -245,7 +242,7 @@ ol.renderer.webgl.Map.prototype.bindImageTexture = var texture = gl.createTexture(); gl.bindTexture(goog.webgl.TEXTURE_2D, texture); gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, image); + goog.webgl.UNSIGNED_BYTE, tile.getImage()); gl.texParameteri( goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri( @@ -254,7 +251,7 @@ ol.renderer.webgl.Map.prototype.bindImageTexture = goog.webgl.CLAMP_TO_EDGE); gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE); - this.textureCache_[imageKey] = { + this.textureCache_[tileKey] = { texture: texture, magFilter: magFilter, minFilter: minFilter @@ -263,75 +260,10 @@ ol.renderer.webgl.Map.prototype.bindImageTexture = }; -/** - * @inheritDoc - */ -ol.renderer.webgl.Map.prototype.canRotate = goog.functions.TRUE; - - -/** - * @param {number} value Hue value. - * @return {!goog.vec.Mat4.Float32} Matrix. - */ -ol.renderer.webgl.Map.prototype.createHueRotateMatrix = function(value) { - var cosHue = Math.cos(value); - var sinHue = Math.sin(value); - var v00 = 0.213 + cosHue * 0.787 - sinHue * 0.213; - var v01 = 0.715 - cosHue * 0.715 - sinHue * 0.715; - var v02 = 0.072 - cosHue * 0.072 + sinHue * 0.928; - var v03 = 0; - var v10 = 0.213 - cosHue * 0.213 + sinHue * 0.143; - var v11 = 0.715 + cosHue * 0.285 + sinHue * 0.140; - var v12 = 0.072 - cosHue * 0.072 - sinHue * 0.283; - var v13 = 0; - var v20 = 0.213 - cosHue * 0.213 - sinHue * 0.787; - var v21 = 0.715 - cosHue * 0.715 + sinHue * 0.715; - var v22 = 0.072 + cosHue * 0.928 + sinHue * 0.072; - var v23 = 0; - var v30 = 0; - var v31 = 0; - var v32 = 0; - var v33 = 1; - var matrix = goog.vec.Mat4.createFloat32(); - goog.vec.Mat4.setFromValues(matrix, - v00, v10, v20, v30, - v01, v11, v21, v31, - v02, v12, v22, v32, - v03, v13, v23, v33); - return matrix; -}; - - -/** - * @param {number} value Brightness value. - * @return {!goog.vec.Mat4.Float32} Matrix. - */ -ol.renderer.webgl.Map.prototype.createBrightnessMatrix = function(value) { - var matrix = goog.vec.Mat4.createFloat32Identity(); - goog.vec.Mat4.setColumnValues(matrix, 3, value, value, value, 1); - return matrix; -}; - - -/** - * @param {number} value Contrast value. - * @return {!goog.vec.Mat4.Float32} Matrix. - */ -ol.renderer.webgl.Map.prototype.createContrastMatrix = function(value) { - var matrix = goog.vec.Mat4.createFloat32(); - goog.vec.Mat4.setDiagonalValues(matrix, value, value, value, 1); - var translateValue = (-0.5 * value + 0.5); - goog.vec.Mat4.setColumnValues(matrix, 3, - translateValue, translateValue, translateValue, 1); - return matrix; -}; - - /** * @inheritDoc */ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { - var gl = this.getGL(); if (layer instanceof ol.layer.TileLayer) { return new ol.renderer.webgl.TileLayer(this, layer); } else { @@ -341,38 +273,6 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { }; -/** - * @param {number} value Saturation value. - * @return {!goog.vec.Mat4.Float32} Matrix. - */ -ol.renderer.webgl.Map.prototype.createSaturateMatrix = function(value) { - var v00 = 0.213 + 0.787 * value; - var v01 = 0.715 - 0.715 * value; - var v02 = 0.072 - 0.072 * value; - var v03 = 0; - var v10 = 0.213 - 0.213 * value; - var v11 = 0.715 + 0.285 * value; - var v12 = 0.072 - 0.072 * value; - var v13 = 0; - var v20 = 0.213 - 0.213 * value; - var v21 = 0.715 - 0.715 * value; - var v22 = 0.072 + 0.928 * value; - var v23 = 0; - var v30 = 0; - var v31 = 0; - var v32 = 0; - var v33 = 1; - var matrix = goog.vec.Mat4.createFloat32(); - goog.vec.Mat4.setFromValues(matrix, - v00, v10, v20, v30, - v01, v11, v21, v31, - v02, v12, v22, v32, - v03, v13, v23, v33); - return matrix; - -}; - - /** * @inheritDoc */ @@ -464,21 +364,6 @@ ol.renderer.webgl.Map.prototype.getShader = function(shaderObject) { * @inheritDoc */ ol.renderer.webgl.Map.prototype.handleBackgroundColorChanged = function() { - var backgroundColor = this.getMap().getBackgroundColor(); - this.clearColor_ = new ol.Color( - backgroundColor.r / 255, - backgroundColor.g / 255, - backgroundColor.b / 255, - backgroundColor.a / 255); - this.getMap().render(); -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.Map.prototype.handleCenterChanged = function() { - goog.base(this, 'handleCenterChanged'); this.getMap().render(); }; @@ -492,33 +377,6 @@ ol.renderer.webgl.Map.prototype.handleLayerRendererChange = function(event) { }; -/** - * @inheritDoc - */ -ol.renderer.webgl.Map.prototype.handleResolutionChanged = function() { - goog.base(this, 'handleResolutionChanged'); - this.getMap().render(); -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.Map.prototype.handleRotationChanged = function() { - goog.base(this, 'handleRotationChanged'); - this.getMap().render(); -}; - - -/** - * @inheritDoc - */ -ol.renderer.webgl.Map.prototype.handleSizeChanged = function() { - goog.base(this, 'handleSizeChanged'); - this.getMap().render(); -}; - - /** * @param {goog.events.Event} event Event. * @protected @@ -565,11 +423,11 @@ ol.renderer.webgl.Map.prototype.initializeGL_ = function() { /** - * @param {Image} image Image. - * @return {boolean} Is image texture loaded. + * @param {ol.Tile} tile Tile. + * @return {boolean} Is tile texture loaded. */ -ol.renderer.webgl.Map.prototype.isImageTextureLoaded = function(image) { - return image.src in this.textureCache_; +ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { + return tile.getKey() in this.textureCache_; }; @@ -601,40 +459,38 @@ ol.renderer.webgl.Map.prototype.removeLayerRenderer = function(layer) { /** * @inheritDoc */ -ol.renderer.webgl.Map.prototype.renderFrame = function(time) { +ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { - if (!this.getMap().isDef()) { - return; + var gl = this.getGL(); + + if (goog.isNull(frameState)) { + if (this.renderedVisible_) { + goog.style.showElement(this.canvas_, false); + this.renderedVisible_ = false; + } + return false; } - var requestRenderFrame = false; - - this.forEachReadyVisibleLayer(function(layer, layerRenderer) { - if (layerRenderer.renderFrame(time)) { - requestRenderFrame = true; + goog.array.forEach(frameState.layersArray, function(layer) { + var layerState = frameState.layerStates[goog.getUid(layer)]; + if (!layerState.visible || !layerState.ready) { + return; } - }); + var layerRenderer = this.getLayerRenderer(layer); + layerRenderer.renderFrame(frameState, layerState); + }, this); - var size = /** @type {ol.Size} */ this.getMap().getSize(); + var size = frameState.size; if (!this.canvasSize_.equals(size)) { this.canvas_.width = size.width; this.canvas_.height = size.height; this.canvasSize_ = size; } - var animate = false; - this.forEachReadyVisibleLayer(function(layer, layerRenderer) { - if (layerRenderer.renderFrame(time)) { - animate = true; - } - }); - - var gl = this.getGL(); - gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, null); - gl.clearColor(this.clearColor_.r, this.clearColor_.g, this.clearColor_.b, - this.clearColor_.a); + var clearColor = frameState.backgroundColor; + gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); gl.clear(goog.webgl.COLOR_BUFFER_BIT); gl.enable(goog.webgl.BLEND); gl.viewport(0, 0, size.width, size.height); @@ -674,28 +530,28 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(time) { this.locations_.aTexCoord, 2, goog.webgl.FLOAT, false, 16, 8); gl.uniform1i(this.locations_.uTexture, 0); - this.forEachReadyVisibleLayer(function(layer, layerRenderer) { + goog.array.forEach(frameState.layersArray, function(layer) { + var layerState = frameState.layerStates[goog.getUid(layer)]; + if (!layerState.visible || !layerState.ready) { + return; + } + var layerRenderer = this.getLayerRenderer(layer); gl.uniformMatrix4fv( this.locations_.uMatrix, false, layerRenderer.getMatrix()); - var hueRotateMatrix = this.createHueRotateMatrix(layer.getHue()); - var saturateMatrix = this.createSaturateMatrix(layer.getSaturation()); - var brightnessMatrix = this.createBrightnessMatrix(layer.getBrightness()); - var contrastMatrix = this.createContrastMatrix(layer.getContrast()); - var colorMatrix = goog.vec.Mat4.createFloat32Identity(); - goog.vec.Mat4.multMat(colorMatrix, contrastMatrix, colorMatrix); - goog.vec.Mat4.multMat(colorMatrix, brightnessMatrix, colorMatrix); - goog.vec.Mat4.multMat(colorMatrix, saturateMatrix, colorMatrix); - goog.vec.Mat4.multMat(colorMatrix, hueRotateMatrix, colorMatrix); - gl.uniformMatrix4fv(this.locations_.uColorMatrix, false, colorMatrix); + gl.uniformMatrix4fv( + this.locations_.uColorMatrix, false, layerRenderer.getColorMatrix()); gl.uniform1f(this.locations_.uOpacity, layer.getOpacity()); gl.bindTexture(goog.webgl.TEXTURE_2D, layerRenderer.getTexture()); gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); }, this); - if (requestRenderFrame) { - this.getMap().requestRenderFrame(); + if (!this.renderedVisible_) { + goog.style.showElement(this.canvas_, true); + this.renderedVisible_ = true; } + this.calculateMatrices2D(frameState); + }; diff --git a/src/ol/renderer/webgl/webglrenderer.js b/src/ol/renderer/webgl/webglrenderer.js index c8eae9c377..d7ba243f8f 100644 --- a/src/ol/renderer/webgl/webglrenderer.js +++ b/src/ol/renderer/webgl/webglrenderer.js @@ -3,12 +3,6 @@ goog.provide('ol.renderer.webgl'); goog.require('ol.webgl'); -/** - * @define {boolean} Free resources immediately. - */ -ol.renderer.webgl.FREE_RESOURCES_IMMEDIATELY = false; - - /** * @return {boolean} Is supported. */ diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 2b7e9d5b9c..43f83838fd 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -1,7 +1,5 @@ // FIXME large resolutions lead to too large framebuffers :-( // FIXME animated shaders! check in redraw -// FIXME throttle texture uploads -// FIXME prioritize texture uploads goog.provide('ol.renderer.webgl.TileLayer'); goog.provide('ol.renderer.webgl.tilelayerrenderer'); @@ -10,13 +8,14 @@ goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Vertex'); goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.debug.Logger'); goog.require('goog.events.EventType'); goog.require('goog.object'); +goog.require('goog.structs.PriorityQueue'); goog.require('goog.vec.Mat4'); goog.require('goog.vec.Vec4'); goog.require('goog.webgl'); goog.require('ol.Coordinate'); +goog.require('ol.FrameState'); goog.require('ol.Size'); goog.require('ol.TileState'); goog.require('ol.layer.TileLayer'); @@ -88,14 +87,6 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { goog.base(this, mapRenderer, tileLayer); - if (goog.DEBUG) { - /** - * @inheritDoc - */ - this.logger = goog.debug.Logger.getLogger( - 'ol.renderer.webgl.tilelayerrenderer.' + goog.getUid(this)); - } - /** * @private * @type {ol.renderer.webgl.FragmentShader} @@ -145,7 +136,7 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { /** * @private - * @type {goog.vec.Mat4.AnyType} + * @type {!goog.vec.Mat4.Number} */ this.matrix_ = goog.vec.Mat4.createNumber(); @@ -166,11 +157,12 @@ goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); /** + * @param {ol.FrameState} frameState Frame state. * @param {number} framebufferDimension Framebuffer dimension. * @private */ ol.renderer.webgl.TileLayer.prototype.bindFramebuffer_ = - function(framebufferDimension) { + function(frameState, framebufferDimension) { var mapRenderer = this.getMapRenderer(); var gl = mapRenderer.getGL(); @@ -178,33 +170,14 @@ ol.renderer.webgl.TileLayer.prototype.bindFramebuffer_ = if (!goog.isDef(this.framebufferDimension_) || this.framebufferDimension_ != framebufferDimension) { - if (goog.DEBUG) { - this.logger.info('re-sizing framebuffer'); - } - - if (ol.renderer.webgl.FREE_RESOURCES_IMMEDIATELY) { - if (goog.DEBUG) { - this.logger.info('freeing WebGL resources'); - } - if (!gl.isContextLost()) { - gl.deleteFramebuffer(this.framebuffer_); - gl.deleteTexture(this.texture_); - } - } else { - var map = this.getMap(); - goog.events.listenOnce( - map, - ol.MapEventType.POSTRENDER, - goog.partial(function(gl, framebuffer, texture) { - if (goog.DEBUG) { - this.logger.info('freeing WebGL resources on postrender'); - } - if (!gl.isContextLost()) { - gl.deleteFramebuffer(framebuffer); - gl.deleteTexture(texture); - } - }, gl, this.framebuffer_, this.texture_)); - } + var map = this.getMap(); + frameState.postRenderFunctions.push( + goog.partial(function(gl, framebuffer, texture) { + if (!gl.isContextLost()) { + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + } + }, gl, this.framebuffer_, this.texture_)); var texture = gl.createTexture(); gl.bindTexture(goog.webgl.TEXTURE_2D, texture); @@ -247,15 +220,6 @@ ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { }; -/** - * @return {ol.layer.TileLayer} Layer. - * @inheritDoc - */ -ol.renderer.webgl.TileLayer.prototype.getLayer = function() { - return /** @type {ol.layer.TileLayer} */ goog.base(this, 'getLayer'); -}; - - /** * @inheritDoc */ @@ -272,6 +236,14 @@ ol.renderer.webgl.TileLayer.prototype.getTexture = function() { }; +/** + * @return {ol.layer.TileLayer} Tile layer. + */ +ol.renderer.webgl.TileLayer.prototype.getTileLayer = function() { + return /** @type {ol.layer.TileLayer} */ (this.getLayer()); +}; + + /** * @inheritDoc */ @@ -287,28 +259,22 @@ ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { /** * @inheritDoc */ -ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { - - var requestRenderFrame = false; +ol.renderer.webgl.TileLayer.prototype.renderFrame = + function(frameState, layerState) { var mapRenderer = this.getMapRenderer(); - var map = this.getMap(); var gl = mapRenderer.getGL(); - goog.asserts.assert(map.isDef()); - var mapCenter = map.getCenter(); - var mapExtent = map.getExtent(); - var mapResolution = /** @type {number} */ map.getResolution(); - var mapRotatedExtent = map.getRotatedExtent(); - var mapRotation = map.getRotation(); + var view2DState = frameState.view2DState; + var center = view2DState.center; - var tileLayer = this.getLayer(); + var tileLayer = this.getTileLayer(); var tileSource = tileLayer.getTileSource(); var tileGrid = tileSource.getTileGrid(); - var z = tileGrid.getZForResolution(mapResolution); + var z = tileGrid.getZForResolution(view2DState.resolution); var tileResolution = tileGrid.getResolution(z); var tileRange = tileGrid.getTileRangeForExtentAndResolution( - mapRotatedExtent, tileResolution); + frameState.extent, tileResolution); var framebufferExtent; @@ -337,7 +303,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { minX + framebufferExtentSize.width, minY + framebufferExtentSize.height); - this.bindFramebuffer_(framebufferDimension); + this.bindFramebuffer_(frameState, framebufferDimension); gl.viewport(0, 0, framebufferDimension, framebufferDimension); gl.clearColor(0, 0, 0, 0); @@ -382,65 +348,73 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { * @type {Object.>} */ var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; - /** - * @type {Array.} - */ - var imagesToLoad = []; + var findInterimTiles = function(z, tileRange) { + // FIXME this could be more efficient about filling partial holes + var fullyCovered = true; + var tile, tileCoord, tileCoordKey, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoord = new ol.TileCoord(z, x, y); + tileCoordKey = tileCoord.toString(); + if (tilesToDrawByZ[z] && tilesToDrawByZ[z][tileCoordKey]) { + return; + } + tile = tileSource.getTile(tileCoord); + if (!goog.isNull(tile) && + tile.getState() == ol.TileState.LOADED && + mapRenderer.isTileTextureLoaded(tile)) { + if (!tilesToDrawByZ[z]) { + tilesToDrawByZ[z] = {}; + } + tilesToDrawByZ[z][tileCoordKey] = tile; + } else { + fullyCovered = false; + } + } + } + return fullyCovered; + }; + + var tilesToLoad = new goog.structs.PriorityQueue(); var allTilesLoaded = true; + var deltaX, deltaY, priority, tile, tileCenter, tileCoord, tileState, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - tilesToDrawByZ[z] = {}; - tileRange.forEachTileCoord(z, function(tileCoord) { - - var tile = tileSource.getTile(tileCoord); - - if (goog.isNull(tile)) { - return; - } - - var tileState = tile.getState(); - if (tileState == ol.TileState.IDLE) { - tile.load(); - } else if (tileState == ol.TileState.LOADED) { - var image = tile.getImage(); - if (mapRenderer.isImageTextureLoaded(image)) { - tilesToDrawByZ[z][tileCoord.toString()] = tile; - return; - } else { - imagesToLoad.push(image); + tileCoord = new ol.TileCoord(z, x, y); + tile = tileSource.getTile(tileCoord); + if (goog.isNull(tile)) { + continue; } - } else if (tileState == ol.TileState.ERROR) { - return; + + tileState = tile.getState(); + if (tileState == ol.TileState.IDLE) { + tileCenter = tileGrid.getTileCoordCenter(tileCoord); + frameState.tileQueue.enqueue(tile, tileCenter, tileResolution); + } else if (tileState == ol.TileState.LOADED) { + if (mapRenderer.isTileTextureLoaded(tile)) { + tilesToDrawByZ[z][tileCoord.toString()] = tile; + continue; + } else { + tileCenter = tileGrid.getTileCoordCenter(tileCoord); + deltaX = tileCenter.x - center.x; + deltaY = tileCenter.y - center.y; + priority = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + tilesToLoad.enqueue(priority, tile); + } + } else if (tileState == ol.TileState.ERROR) { + continue; + } + + allTilesLoaded = false; + tileGrid.forEachTileCoordParentTileRange(tileCoord, findInterimTiles); + } - allTilesLoaded = false; - - // FIXME this could be more efficient about filling partial holes - tileGrid.forEachTileCoordParentTileRange( - tileCoord, - function(z, tileRange) { - var fullyCovered = true; - tileRange.forEachTileCoord(z, function(tileCoord) { - var tileCoordKey = tileCoord.toString(); - if (tilesToDrawByZ[z] && tilesToDrawByZ[z][tileCoordKey]) { - return; - } - var tile = tileSource.getTile(tileCoord); - if (!goog.isNull(tile) && - tile.getState() == ol.TileState.LOADED) { - if (!tilesToDrawByZ[z]) { - tilesToDrawByZ[z] = {}; - } - tilesToDrawByZ[z][tileCoordKey] = tile; - } else { - fullyCovered = false; - } - }); - return fullyCovered; - }); - - }, this); + } /** @type {Array.} */ var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); @@ -457,29 +431,22 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { framebufferExtentSize.height - 1; goog.vec.Vec4.setFromValues(uTileOffset, sx, sy, tx, ty); gl.uniform4fv(this.locations_.uTileOffset, uTileOffset); - mapRenderer.bindImageTexture( - tile.getImage(), goog.webgl.LINEAR, goog.webgl.LINEAR); + mapRenderer.bindTileTexture(tile, goog.webgl.LINEAR, goog.webgl.LINEAR); gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); }, this); }, this); - if (!goog.array.isEmpty(imagesToLoad)) { - goog.events.listenOnce( - map, - ol.MapEventType.POSTRENDER, - goog.partial(function(mapRenderer, imagesToLoad) { - if (goog.DEBUG) { - this.logger.info( - 'uploading ' + imagesToLoad.length + ' textures'); + if (!tilesToLoad.isEmpty()) { + frameState.postRenderFunctions.push( + goog.partial(function(mapRenderer, tilesToLoad) { + var i, tile; + // FIXME determine a suitable number of textures to upload per frame + for (i = 0; !tilesToLoad.isEmpty() && i < 4; ++i) { + tile = /** @type {ol.Tile} */ (tilesToLoad.remove()); + mapRenderer.bindTileTexture( + tile, goog.webgl.LINEAR, goog.webgl.LINEAR); } - goog.array.forEach(imagesToLoad, function(image) { - mapRenderer.bindImageTexture( - image, goog.webgl.LINEAR, goog.webgl.LINEAR); - }); - if (goog.DEBUG) { - this.logger.info('uploaded textures'); - } - }, mapRenderer, imagesToLoad)); + }, mapRenderer, tilesToLoad)); } if (allTilesLoaded) { @@ -488,25 +455,23 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { } else { this.renderedTileRange_ = null; this.renderedFramebufferExtent_ = null; - requestRenderFrame = true; + frameState.animate = true; } } goog.vec.Mat4.makeIdentity(this.matrix_); goog.vec.Mat4.translate(this.matrix_, - (mapCenter.x - framebufferExtent.minX) / + (view2DState.center.x - framebufferExtent.minX) / (framebufferExtent.maxX - framebufferExtent.minX), - (mapCenter.y - framebufferExtent.minY) / + (view2DState.center.y - framebufferExtent.minY) / (framebufferExtent.maxY - framebufferExtent.minY), 0); - if (goog.isDef(mapRotation)) { - goog.vec.Mat4.rotateZ(this.matrix_, mapRotation); - } + goog.vec.Mat4.rotateZ(this.matrix_, view2DState.rotation); goog.vec.Mat4.scale(this.matrix_, - (mapExtent.maxX - mapExtent.minX) / + frameState.size.width * view2DState.resolution / (framebufferExtent.maxX - framebufferExtent.minX), - (mapExtent.maxY - mapExtent.minY) / + frameState.size.height * view2DState.resolution / (framebufferExtent.maxY - framebufferExtent.minY), 1); goog.vec.Mat4.translate(this.matrix_, @@ -514,6 +479,4 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { -0.5, 0); - return requestRenderFrame; - }; diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js index 1b805faf93..7693cb81d9 100644 --- a/src/ol/source/bingmapssource.js +++ b/src/ol/source/bingmapssource.js @@ -6,7 +6,7 @@ goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.net.Jsonp'); goog.require('ol.TileCoverageArea'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.XYZ'); @@ -25,7 +25,7 @@ ol.BingMapsStyle = { /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.BingMapsOptions} bingMapsOptions Bing Maps options. */ ol.source.BingMaps = function(bingMapsOptions) { @@ -57,7 +57,7 @@ ol.source.BingMaps = function(bingMapsOptions) { }, goog.bind(this.handleImageryMetadataResponse, this)); }; -goog.inherits(ol.source.BingMaps, ol.source.TileSource); +goog.inherits(ol.source.BingMaps, ol.source.ImageTileSource); /** diff --git a/src/ol/source/debugtilesource.js b/src/ol/source/debugtilesource.js new file mode 100644 index 0000000000..cfe149bb20 --- /dev/null +++ b/src/ol/source/debugtilesource.js @@ -0,0 +1,128 @@ +goog.provide('ol.source.DebugTileSource'); +goog.provide('ol.source.DebugTileSourceOptions'); + +goog.require('ol.Size'); +goog.require('ol.Tile'); +goog.require('ol.TileCoord'); +goog.require('ol.source.TileSource'); +goog.require('ol.tilegrid.TileGrid'); + + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @private + */ +ol.DebugTile_ = function(tileCoord, tileGrid) { + + goog.base(this, tileCoord); + + this.state = ol.TileState.LOADED; + + /** + * @private + * @type {ol.TileCoord} + */ + this.tileCoord_ = tileCoord; + + /** + * @private + * @type {ol.Size} + */ + this.tileSize_ = tileGrid.getTileSize(); + + /** + * @private + * @type {Object.} + */ + this.canvasByContext_ = {}; + +}; +goog.inherits(ol.DebugTile_, ol.Tile); + + +/** + * @inheritDoc + */ +ol.DebugTile_.prototype.getImage = function(opt_context) { + var key = goog.isDef(opt_context) ? goog.getUid(opt_context) : -1; + if (key in this.canvasByContext_) { + return this.canvasByContext_[key]; + } else { + + var tileSize = this.tileSize_; + + var canvas = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + canvas.width = tileSize.width; + canvas.height = tileSize.height; + + var context = canvas.getContext('2d'); + + context.strokeStyle = 'black'; + context.strokeRect(0.5, 0.5, tileSize.width + 0.5, tileSize.height + 0.5); + + context.fillStyle = 'black'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = '24px sans-serif'; + context.fillText( + this.tileCoord_.toString(), tileSize.width / 2, tileSize.height / 2); + + this.canvasByContext_[key] = canvas; + return canvas; + + } +}; + + +/** + * @typedef {{extent: (ol.Extent|undefined), + * projection: (ol.Projection|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined)}} + */ +ol.source.DebugTileSourceOptions; + + + +/** + * @constructor + * @extends {ol.source.TileSource} + * @param {ol.source.DebugTileSourceOptions} options Options. + */ +ol.source.DebugTileSource = function(options) { + + goog.base(this, { + extent: options.extent, + projection: options.projection, + tileGrid: options.tileGrid + }); + + /** + * @private + * @type {Object.} + * FIXME will need to expire elements from this cache + * FIXME see elemoine's work with goog.structs.LinkedMap + */ + this.tileCache_ = {}; + +}; +goog.inherits(ol.source.DebugTileSource, ol.source.TileSource); + + +/** + * @inheritDoc + */ +ol.source.DebugTileSource.prototype.getTile = function(tileCoord) { + var key = tileCoord.toString(); + if (goog.object.containsKey(this.tileCache_, key)) { + return this.tileCache_[key]; + } else { + var tile = new ol.DebugTile_(tileCoord, this.tileGrid); + this.tileCache_[key] = tile; + return tile; + } +}; diff --git a/src/ol/source/imagetilesource.js b/src/ol/source/imagetilesource.js new file mode 100644 index 0000000000..5baea4539f --- /dev/null +++ b/src/ol/source/imagetilesource.js @@ -0,0 +1,95 @@ +goog.provide('ol.source.ImageTileSource'); +goog.provide('ol.source.ImageTileSourceOptions'); + +goog.require('ol.Attribution'); +goog.require('ol.Extent'); +goog.require('ol.ImageTile'); +goog.require('ol.Projection'); +goog.require('ol.TileCoord'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.TileUrlFunctionType'); +goog.require('ol.source.TileSource'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @typedef {{attributions: (Array.|undefined), + * crossOrigin: (null|string|undefined), + * extent: (ol.Extent|undefined), + * projection: (ol.Projection|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined), + * tileUrlFunction: (ol.TileUrlFunctionType|undefined)}} + */ +ol.source.ImageTileSourceOptions; + + + +/** + * @constructor + * @extends {ol.source.TileSource} + * @param {ol.source.ImageTileSourceOptions} options Options. + */ +ol.source.ImageTileSource = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + projection: options.projection, + tileGrid: options.tileGrid + }); + + /** + * @protected + * @type {ol.TileUrlFunctionType} + */ + this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ? + options.tileUrlFunction : + ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + goog.isDef(options.crossOrigin) ? options.crossOrigin : 'anonymous'; + + /** + * @private + * @type {Object.} + * FIXME will need to expire elements from this cache + * FIXME see elemoine's work with goog.structs.LinkedMap + */ + this.tileCache_ = {}; + +}; +goog.inherits(ol.source.ImageTileSource, ol.source.TileSource); + + +/** + * @inheritDoc + */ +ol.source.ImageTileSource.prototype.getTile = function(tileCoord) { + var key = tileCoord.toString(); + if (goog.object.containsKey(this.tileCache_, key)) { + return this.tileCache_[key]; + } else { + var tileUrl = this.getTileCoordUrl(tileCoord); + var tile; + if (goog.isDef(tileUrl)) { + tile = new ol.ImageTile(tileCoord, tileUrl, this.crossOrigin_); + } else { + tile = null; + } + this.tileCache_[key] = tile; + return tile; + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {string|undefined} Tile URL. + */ +ol.source.ImageTileSource.prototype.getTileCoordUrl = function(tileCoord) { + return this.tileUrlFunction(tileCoord); +}; diff --git a/src/ol/source/stamensource.js b/src/ol/source/stamensource.js index d4549b8c95..089f8ece76 100644 --- a/src/ol/source/stamensource.js +++ b/src/ol/source/stamensource.js @@ -26,7 +26,7 @@ ol.source.StamenFlavor = { /** - * @typedef {{flavor: ol.source.StamenFlavor, + * @typedef {{flavor: (ol.source.StamenFlavor|undefined), * provider: ol.source.StamenProvider}} */ ol.source.StamenOptions; diff --git a/src/ol/source/tiledwmssource.js b/src/ol/source/tiledwmssource.js index b3bddea36a..40b0c699ff 100644 --- a/src/ol/source/tiledwmssource.js +++ b/src/ol/source/tiledwmssource.js @@ -10,14 +10,14 @@ goog.require('ol.Attribution'); goog.require('ol.Projection'); goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.TileGrid'); /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.TiledWMSOptions} tiledWMSOptions options. */ ol.source.TiledWMS = function(tiledWMSOptions) { @@ -35,20 +35,8 @@ ol.source.TiledWMS = function(tiledWMSOptions) { if (goog.isDef(tiledWMSOptions.tileGrid)) { tileGrid = tiledWMSOptions.tileGrid; } else { - // FIXME Factor this out to a more central/generic place. - var size = Math.max( - projectionExtent.maxX - projectionExtent.minX, - projectionExtent.maxY - projectionExtent.minY); - var maxZoom = goog.isDef(tiledWMSOptions.maxZoom) ? - tiledWMSOptions.maxZoom : 18; - var resolutions = new Array(maxZoom + 1); - for (var z = 0, zz = resolutions.length; z < zz; ++z) { - resolutions[z] = ol.Projection.EPSG_3857_HALF_SIZE / (128 << z); - } - tileGrid = new ol.tilegrid.TileGrid({ - origin: projectionExtent.getTopLeft(), - resolutions: resolutions - }); + tileGrid = ol.tilegrid.createForProjection(projection, + tiledWMSOptions.maxZoom); } var baseParams = { @@ -116,4 +104,4 @@ ol.source.TiledWMS = function(tiledWMSOptions) { }); }; -goog.inherits(ol.source.TiledWMS, ol.source.TileSource); +goog.inherits(ol.source.TiledWMS, ol.source.ImageTileSource); diff --git a/src/ol/source/tilejsonsource.js b/src/ol/source/tilejsonsource.js index 35c9124468..81eacaf7a4 100644 --- a/src/ol/source/tilejsonsource.js +++ b/src/ol/source/tilejsonsource.js @@ -16,7 +16,7 @@ goog.require('goog.string'); goog.require('ol.Projection'); goog.require('ol.TileCoverageArea'); goog.require('ol.TileUrlFunction'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.XYZ'); @@ -45,7 +45,7 @@ goog.exportSymbol('grid', grid); /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.TileJSONOptions} tileJsonOptions TileJSON optios. */ ol.source.TileJSON = function(tileJsonOptions) { @@ -69,7 +69,7 @@ ol.source.TileJSON = function(tileJsonOptions) { this.deferred_.addCallback(this.handleTileJSONResponse, this); }; -goog.inherits(ol.source.TileJSON, ol.source.TileSource); +goog.inherits(ol.source.TileJSON, ol.source.ImageTileSource); /** diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index 9b915efe16..6b82b8f3b8 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -14,11 +14,9 @@ goog.require('ol.tilegrid.TileGrid'); /** * @typedef {{attributions: (Array.|undefined), - * crossOrigin: (null|string|undefined), * extent: (ol.Extent|undefined), * projection: (ol.Projection|undefined), - * tileGrid: (ol.tilegrid.TileGrid|undefined), - * tileUrlFunction: (ol.TileUrlFunctionType|undefined)}} + * tileGrid: (ol.tilegrid.TileGrid|undefined)}} */ ol.source.TileSourceOptions; @@ -44,29 +42,6 @@ ol.source.TileSource = function(tileSourceOptions) { this.tileGrid = goog.isDef(tileSourceOptions.tileGrid) ? tileSourceOptions.tileGrid : null; - /** - * @protected - * @type {ol.TileUrlFunctionType} - */ - this.tileUrlFunction = goog.isDef(tileSourceOptions.tileUrlFunction) ? - tileSourceOptions.tileUrlFunction : - ol.TileUrlFunction.nullTileUrlFunction; - - /** - * @private - * @type {?string} - */ - this.crossOrigin_ = goog.isDef(tileSourceOptions.crossOrigin) ? - tileSourceOptions.crossOrigin : 'anonymous'; - - /** - * @private - * @type {Object.} - * FIXME will need to expire elements from this cache - * FIXME see elemoine's work with goog.structs.LinkedMap - */ - this.tileCache_ = {}; - }; goog.inherits(ol.source.TileSource, ol.source.Source); @@ -83,31 +58,7 @@ ol.source.TileSource.prototype.getResolutions = function() { * @param {ol.TileCoord} tileCoord Tile coordinate. * @return {ol.Tile} Tile. */ -ol.source.TileSource.prototype.getTile = function(tileCoord) { - var key = tileCoord.toString(); - if (goog.object.containsKey(this.tileCache_, key)) { - return this.tileCache_[key]; - } else { - var tileUrl = this.getTileCoordUrl(tileCoord); - var tile; - if (goog.isDef(tileUrl)) { - tile = new ol.Tile(tileCoord, tileUrl, this.crossOrigin_); - } else { - tile = null; - } - this.tileCache_[key] = tile; - return tile; - } -}; - - -/** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {string|undefined} Tile URL. - */ -ol.source.TileSource.prototype.getTileCoordUrl = function(tileCoord) { - return this.tileUrlFunction(tileCoord); -}; +ol.source.TileSource.prototype.getTile = goog.abstractMethod; /** diff --git a/src/ol/source/xyzsource.js b/src/ol/source/xyzsource.js index f1ed03cc3b..0758d99f89 100644 --- a/src/ol/source/xyzsource.js +++ b/src/ol/source/xyzsource.js @@ -11,7 +11,7 @@ goog.require('ol.Size'); goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); goog.require('ol.TileUrlFunctionType'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.XYZ'); @@ -31,7 +31,7 @@ ol.source.XYZOptions; /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.XYZOptions} xyzOptions XYZ options. */ ol.source.XYZ = function(xyzOptions) { @@ -110,4 +110,4 @@ ol.source.XYZ = function(xyzOptions) { }); }; -goog.inherits(ol.source.XYZ, ol.source.TileSource); +goog.inherits(ol.source.XYZ, ol.source.ImageTileSource); diff --git a/src/ol/tile.js b/src/ol/tile.js index a4cde0f0e1..180a712065 100644 --- a/src/ol/tile.js +++ b/src/ol/tile.js @@ -24,10 +24,8 @@ ol.TileState = { * @constructor * @extends {goog.events.EventTarget} * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {string} src Image source URI. - * @param {?string} crossOrigin Cross origin. */ -ol.Tile = function(tileCoord, src, crossOrigin) { +ol.Tile = function(tileCoord) { goog.base(this); @@ -37,39 +35,10 @@ ol.Tile = function(tileCoord, src, crossOrigin) { this.tileCoord = tileCoord; /** - * Image URI - * - * @private - * @type {string} - */ - this.src_ = src; - - /** - * @private + * @protected * @type {ol.TileState} */ - this.state_ = ol.TileState.IDLE; - - /** - * @private - * @type {Image} - */ - this.image_ = new Image(); - if (!goog.isNull(crossOrigin)) { - this.image_.crossOrigin = crossOrigin; - } - - /** - * @private - * @type {Object.} - */ - this.imageByContext_ = {}; - - /** - * @private - * @type {Array.} - */ - this.imageListenerKeys_ = null; + this.state = ol.TileState.IDLE; }; goog.inherits(ol.Tile, goog.events.EventTarget); @@ -85,24 +54,16 @@ ol.Tile.prototype.dispatchChangeEvent = function() { /** * @param {Object=} opt_context Object. - * @return {Image} Image. + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. */ -ol.Tile.prototype.getImage = function(opt_context) { - if (goog.isDef(opt_context)) { - var image; - var key = goog.getUid(opt_context); - if (key in this.imageByContext_) { - return this.imageByContext_[key]; - } else if (goog.object.isEmpty(this.imageByContext_)) { - image = this.image_; - } else { - image = /** @type {Image} */ this.image_.cloneNode(false); - } - this.imageByContext_[key] = image; - return image; - } else { - return this.image_; - } +ol.Tile.prototype.getImage = goog.abstractMethod; + + +/** + * @return {string} Key. + */ +ol.Tile.prototype.getKey = function() { + return goog.getUid(this).toString(); }; @@ -110,59 +71,11 @@ ol.Tile.prototype.getImage = function(opt_context) { * @return {ol.TileState} State. */ ol.Tile.prototype.getState = function() { - return this.state_; + return this.state; }; /** - * Tracks loading or read errors. - * - * @private + * FIXME empty description for jsdoc */ -ol.Tile.prototype.handleImageError_ = function() { - this.state_ = ol.TileState.ERROR; - this.unlistenImage_(); -}; - - -/** - * Tracks successful image load. - * - * @private - */ -ol.Tile.prototype.handleImageLoad_ = function() { - this.state_ = ol.TileState.LOADED; - this.unlistenImage_(); - this.dispatchChangeEvent(); -}; - - -/** - * Load not yet loaded URI. - */ -ol.Tile.prototype.load = function() { - if (this.state_ == ol.TileState.IDLE) { - this.state_ = ol.TileState.LOADING; - goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); - this.imageListenerKeys_ = [ - goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, - this.handleImageError_, false, this), - goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, - this.handleImageLoad_, false, this) - ]; - this.image_.src = this.src_; - } -}; - - -/** - * Discards event handlers which listen for load completion or errors. - * - * @private - */ -ol.Tile.prototype.unlistenImage_ = function() { - goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); - goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); - this.imageListenerKeys_ = null; -}; - +ol.Tile.prototype.load = goog.abstractMethod; diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index bdce78ae2d..8c0f0a1e5d 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -316,3 +316,26 @@ ol.tilegrid.TileGrid.prototype.getTileSize = function() { ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) { return ol.array.linearFindNearest(this.resolutions_, resolution); }; + + +/** + * @param {ol.Projection} projection Projection. + * @param {number=} opt_maxZoom Maximum zoom level (optional). Default is 18. + * @return {ol.tilegrid.TileGrid} TileGrid instance. + */ +ol.tilegrid.createForProjection = function(projection, opt_maxZoom) { + var projectionExtent = projection.getExtent(); + var size = Math.max( + projectionExtent.maxX - projectionExtent.minX, + projectionExtent.maxY - projectionExtent.minY); + var maxZoom = goog.isDef(opt_maxZoom) ? + opt_maxZoom : 18; + var resolutions = new Array(maxZoom + 1); + for (var z = 0, zz = resolutions.length; z < zz; ++z) { + resolutions[z] = size / (256 << z); + } + return new ol.tilegrid.TileGrid({ + origin: projectionExtent.getTopLeft(), + resolutions: resolutions + }); +}; diff --git a/src/ol/tilequeue.js b/src/ol/tilequeue.js new file mode 100644 index 0000000000..d73f7c0901 --- /dev/null +++ b/src/ol/tilequeue.js @@ -0,0 +1,120 @@ +goog.provide('ol.TilePriorityFunction'); +goog.provide('ol.TileQueue'); + +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.structs.PriorityQueue'); +goog.require('ol.Coordinate'); +goog.require('ol.Tile'); +goog.require('ol.TileState'); + + +/** + * @typedef {function(ol.Tile, ol.Coordinate, number): (number|undefined)} + */ +ol.TilePriorityFunction; + + + +/** + * @constructor + * @param {ol.TilePriorityFunction} tilePriorityFunction + * Tile priority function. + */ +ol.TileQueue = function(tilePriorityFunction) { + + /** + * @private + * @type {ol.TilePriorityFunction} + */ + this.tilePriorityFunction_ = tilePriorityFunction; + + /** + * @private + * @type {number} + */ + this.maxTilesLoading_ = 8; + + /** + * @private + * @type {number} + */ + this.tilesLoading_ = 0; + + /** + * @private + * @type {goog.structs.PriorityQueue} + */ + this.queue_ = new goog.structs.PriorityQueue(); + + /** + * @private + * @type {Object.} + */ + this.queuedTileKeys_ = {}; + +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + */ +ol.TileQueue.prototype.enqueue = + function(tile, tileCenter, tileResolution) { + if (tile.getState() != ol.TileState.IDLE) { + return; + } + var tileKey = tile.getKey(); + if (!(tileKey in this.queuedTileKeys_)) { + var priority = this.tilePriorityFunction_(tile, tileCenter, tileResolution); + if (goog.isDef(priority)) { + this.queue_.enqueue(priority, arguments); + this.queuedTileKeys_[tileKey] = true; + } else { + // FIXME fire drop event? + } + } +}; + + +/** + * @protected + */ +ol.TileQueue.prototype.handleTileChange = function() { + --this.tilesLoading_; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.TileQueue.prototype.loadMoreTiles = function() { + var tile, tileKey; + while (!this.queue_.isEmpty() && this.tilesLoading_ < this.maxTilesLoading_) { + tile = (/** @type {Array} */ (this.queue_.dequeue()))[0]; + tileKey = tile.getKey(); + delete this.queuedTileKeys_[tileKey]; + goog.events.listen(tile, goog.events.EventType.CHANGE, + this.handleTileChange, false, this); + tile.load(); + ++this.tilesLoading_; + } +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.TileQueue.prototype.reprioritize = function() { + if (!this.queue_.isEmpty()) { + var values = /** @type {Array.} */ (this.queue_.getValues()); + this.queue_.clear(); + this.queuedTileKeys_ = {}; + var i; + for (i = 0; i < values.length; ++i) { + this.enqueue.apply(this, values[i]); + } + } +}; diff --git a/src/ol/tilerange.js b/src/ol/tilerange.js index db4d13f051..7c402f00cb 100644 --- a/src/ol/tilerange.js +++ b/src/ol/tilerange.js @@ -28,9 +28,9 @@ ol.TileRange.boundingTileRange = function(var_args) { var tileCoord0 = arguments[0]; var tileRange = new ol.TileRange(tileCoord0.x, tileCoord0.y, tileCoord0.x, tileCoord0.y); - var i; + var i, tileCoord; for (i = 1; i < arguments.length; ++i) { - var tileCoord = arguments[i]; + tileCoord = arguments[i]; goog.asserts.assert(tileCoord.z == tileCoord0.z); tileRange.minX = Math.min(tileRange.minX, tileCoord.x); tileRange.minY = Math.min(tileRange.minY, tileCoord.y); @@ -71,22 +71,6 @@ ol.TileRange.prototype.equals = function(tileRange) { }; -/** - * @param {number} z Z. - * @param {function(this: T, ol.TileCoord)} f Callback. - * @param {T=} opt_obj The object to be used for the value of 'this' within f. - * @template T - */ -ol.TileRange.prototype.forEachTileCoord = function(z, f, opt_obj) { - var x, y; - for (x = this.minX; x <= this.maxX; ++x) { - for (y = this.minY; y <= this.maxY; ++y) { - f.call(opt_obj, new ol.TileCoord(z, x, y)); - } - } -}; - - /** * @inheritDoc * @return {number} Height. diff --git a/src/ol/vec/mat4.js b/src/ol/vec/mat4.js new file mode 100644 index 0000000000..46535be877 --- /dev/null +++ b/src/ol/vec/mat4.js @@ -0,0 +1,92 @@ +goog.provide('ol.vec.Mat4'); + +goog.require('goog.vec.Mat4'); + + +/** + * @param {!goog.vec.Mat4.Float32} matrix Matrix. + * @param {number} value Brightness value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.vec.Mat4.makeBrightness = function(matrix, value) { + goog.vec.Mat4.makeTranslate(matrix, value, value, value); + return matrix; +}; + + +/** + * @param {!goog.vec.Mat4.Float32} matrix Matrix. + * @param {number} value Contrast value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.vec.Mat4.makeContrast = function(matrix, value) { + goog.vec.Mat4.makeScale(matrix, value, value, value); + var translateValue = (-0.5 * value + 0.5); + goog.vec.Mat4.setColumnValues(matrix, 3, + translateValue, translateValue, translateValue, 1); + return matrix; +}; + + +/** + * @param {!goog.vec.Mat4.Float32} matrix Matrix. + * @param {number} value Hue value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.vec.Mat4.makeHue = function(matrix, value) { + var cosHue = Math.cos(value); + var sinHue = Math.sin(value); + var v00 = 0.213 + cosHue * 0.787 - sinHue * 0.213; + var v01 = 0.715 - cosHue * 0.715 - sinHue * 0.715; + var v02 = 0.072 - cosHue * 0.072 + sinHue * 0.928; + var v03 = 0; + var v10 = 0.213 - cosHue * 0.213 + sinHue * 0.143; + var v11 = 0.715 + cosHue * 0.285 + sinHue * 0.140; + var v12 = 0.072 - cosHue * 0.072 - sinHue * 0.283; + var v13 = 0; + var v20 = 0.213 - cosHue * 0.213 - sinHue * 0.787; + var v21 = 0.715 - cosHue * 0.715 + sinHue * 0.715; + var v22 = 0.072 + cosHue * 0.928 + sinHue * 0.072; + var v23 = 0; + var v30 = 0; + var v31 = 0; + var v32 = 0; + var v33 = 1; + goog.vec.Mat4.setFromValues(matrix, + v00, v10, v20, v30, + v01, v11, v21, v31, + v02, v12, v22, v32, + v03, v13, v23, v33); + return matrix; +}; + + +/** + * @param {!goog.vec.Mat4.Float32} matrix Matrix. + * @param {number} value Saturation value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.vec.Mat4.makeSaturation = function(matrix, value) { + var v00 = 0.213 + 0.787 * value; + var v01 = 0.715 - 0.715 * value; + var v02 = 0.072 - 0.072 * value; + var v03 = 0; + var v10 = 0.213 - 0.213 * value; + var v11 = 0.715 + 0.285 * value; + var v12 = 0.072 - 0.072 * value; + var v13 = 0; + var v20 = 0.213 - 0.213 * value; + var v21 = 0.715 - 0.715 * value; + var v22 = 0.072 + 0.928 * value; + var v23 = 0; + var v30 = 0; + var v31 = 0; + var v32 = 0; + var v33 = 1; + goog.vec.Mat4.setFromValues(matrix, + v00, v10, v20, v30, + v01, v11, v21, v31, + v02, v12, v22, v32, + v03, v13, v23, v33); + return matrix; +}; diff --git a/src/ol/view.js b/src/ol/view.js new file mode 100644 index 0000000000..27e0c9db1f --- /dev/null +++ b/src/ol/view.js @@ -0,0 +1,65 @@ +goog.provide('ol.View'); +goog.provide('ol.ViewHint'); + +goog.require('goog.array'); +goog.require('ol.IView'); +goog.require('ol.IView2D'); +goog.require('ol.IView3D'); + + +/** + * @enum {number} + */ +ol.ViewHint = { + ANIMATING: 0, + PANNING: 1 +}; + + + +/** + * @constructor + * @implements {ol.IView} + * @extends {ol.Object} + */ +ol.View = function() { + + /** + * @private + * @type {Array.} + */ + this.hints_ = [0, 0]; + +}; +goog.inherits(ol.View, ol.Object); + + +/** + * @return {Array.} Hint. + */ +ol.View.prototype.getHints = function() { + return goog.array.clone(this.hints_); +}; + + +/** + * @inheritDoc + */ +ol.View.prototype.getView2D = goog.abstractMethod; + + +/** + * @inheritDoc + */ +ol.View.prototype.getView3D = goog.abstractMethod; + + +/** + * @param {ol.ViewHint} hint Hint. + * @param {number} delta Delta. + */ +ol.View.prototype.setHint = function(hint, delta) { + goog.asserts.assert(0 <= hint && hint < this.hints_.length); + this.hints_[hint] += delta; + goog.asserts.assert(this.hints_[hint] >= 0); +}; diff --git a/src/ol/view2d.js b/src/ol/view2d.js new file mode 100644 index 0000000000..d9e256b881 --- /dev/null +++ b/src/ol/view2d.js @@ -0,0 +1,344 @@ +// FIXME getView3D has not return type +// FIXME remove getExtent? + +goog.provide('ol.View2D'); +goog.provide('ol.View2DProperty'); + +goog.require('ol.Constraints'); +goog.require('ol.Extent'); +goog.require('ol.IView2D'); +goog.require('ol.IView3D'); +goog.require('ol.Projection'); +goog.require('ol.ResolutionConstraint'); +goog.require('ol.RotationConstraint'); +goog.require('ol.View'); + + +/** + * @enum {string} + */ +ol.View2DProperty = { + CENTER: 'center', + PROJECTION: 'projection', + RESOLUTION: 'resolution', + ROTATION: 'rotation' +}; + + + +/** + * @constructor + * @implements {ol.IView2D} + * @implements {ol.IView3D} + * @extends {ol.View} + * @param {ol.View2DOptions=} opt_view2DOptions View2D options. + */ +ol.View2D = function(opt_view2DOptions) { + goog.base(this); + var view2DOptions = opt_view2DOptions || {}; + + /** + * @type {Object.} + */ + var values = {}; + values[ol.View2DProperty.CENTER] = goog.isDef(view2DOptions.center) ? + view2DOptions.center : null; + values[ol.View2DProperty.PROJECTION] = ol.Projection.createProjection( + view2DOptions.projection, 'EPSG:3857'); + if (goog.isDef(view2DOptions.resolution)) { + values[ol.View2DProperty.RESOLUTION] = view2DOptions.resolution; + } else if (goog.isDef(view2DOptions.zoom)) { + var projectionExtent = values[ol.View2DProperty.PROJECTION].getExtent(); + var size = Math.max( + projectionExtent.maxX - projectionExtent.minX, + projectionExtent.maxY - projectionExtent.minY); + values[ol.View2DProperty.RESOLUTION] = size / (256 << view2DOptions.zoom); + } + values[ol.View2DProperty.ROTATION] = view2DOptions.rotation; + this.setValues(values); + + /** + * @private + * @type {ol.Constraints} + */ + this.constraints_ = ol.View2D.createConstraints_(view2DOptions); + +}; +goog.inherits(ol.View2D, ol.View); + + +/** + * @inheritDoc + */ +ol.View2D.prototype.getCenter = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.View2DProperty.CENTER)); +}; +goog.exportProperty( + ol.View2D.prototype, + 'getCenter', + ol.View2D.prototype.getCenter); + + +/** + * @param {ol.Size} size Box pixel size. + * @return {ol.Extent} Extent. + */ +ol.View2D.prototype.getExtent = function(size) { + goog.asserts.assert(this.isDef()); + var center = this.getCenter(); + var resolution = this.getResolution(); + var minX = center.x - resolution * size.width / 2; + var minY = center.y - resolution * size.height / 2; + var maxX = center.x + resolution * size.width / 2; + var maxY = center.y + resolution * size.height / 2; + return new ol.Extent(minX, minY, maxX, maxY); +}; + + +/** + * @inheritDoc + */ +ol.View2D.prototype.getProjection = function() { + return /** @type {ol.Projection|undefined} */ ( + this.get(ol.View2DProperty.PROJECTION)); +}; +goog.exportProperty( + ol.View2D.prototype, + 'getProjection', + ol.View2D.prototype.getProjection); + + +/** + * @inheritDoc + */ +ol.View2D.prototype.getResolution = function() { + return /** @type {number|undefined} */ ( + this.get(ol.View2DProperty.RESOLUTION)); +}; +goog.exportProperty( + ol.View2D.prototype, + 'getResolution', + ol.View2D.prototype.getResolution); + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Box pixel size. + * @return {number} Resolution. + */ +ol.View2D.prototype.getResolutionForExtent = function(extent, size) { + var xResolution = (extent.maxX - extent.minX) / size.width; + var yResolution = (extent.maxY - extent.minY) / size.height; + return Math.max(xResolution, yResolution); +}; + + +/** + * @return {number} Map rotation. + */ +ol.View2D.prototype.getRotation = function() { + return /** @type {number|undefined} */ ( + this.get(ol.View2DProperty.ROTATION)) || 0; +}; +goog.exportProperty( + ol.View2D.prototype, + 'getRotation', + ol.View2D.prototype.getRotation); + + +/** + * @inheritDoc + */ +ol.View2D.prototype.getView2D = function() { + return this; +}; + + +/** + * @inheritDoc + */ +ol.View2D.prototype.getView2DState = function() { + goog.asserts.assert(this.isDef()); + var center = /** @type {ol.Coordinate} */ (this.getCenter()); + var projection = /** @type {ol.Projection} */ (this.getProjection()); + var resolution = /** @type {number} */ (this.getResolution()); + var rotation = /** @type {number} */ (this.getRotation()); + return { + center: new ol.Coordinate(center.x, center.y), + projection: projection, + resolution: resolution, + rotation: rotation + }; +}; + + +/** + * FIXME return type + */ +ol.View2D.prototype.getView3D = function() { +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Box pixel size. + */ +ol.View2D.prototype.fitExtent = function(extent, size) { + this.setCenter(extent.getCenter()); + var resolution = this.getResolutionForExtent(extent, size); + resolution = this.constraints_.resolution(resolution, 0); + this.setResolution(resolution); +}; + + +/** + * @return {boolean} Is defined. + */ +ol.View2D.prototype.isDef = function() { + return goog.isDefAndNotNull(this.getCenter()) && + goog.isDef(this.getResolution()); +}; + + +/** + * @param {ol.Coordinate|undefined} center Center. + */ +ol.View2D.prototype.setCenter = function(center) { + this.set(ol.View2DProperty.CENTER, center); +}; +goog.exportProperty( + ol.View2D.prototype, + 'setCenter', + ol.View2D.prototype.setCenter); + + +/** + * @param {ol.Projection|undefined} projection Projection. + */ +ol.View2D.prototype.setProjection = function(projection) { + this.set(ol.View2DProperty.PROJECTION, projection); +}; +goog.exportProperty( + ol.View2D.prototype, + 'setProjection', + ol.View2D.prototype.setProjection); + + +/** + * @param {number|undefined} resolution Resolution. + */ +ol.View2D.prototype.setResolution = function(resolution) { + this.set(ol.View2DProperty.RESOLUTION, resolution); +}; +goog.exportProperty( + ol.View2D.prototype, + 'setResolution', + ol.View2D.prototype.setResolution); + + +/** + * @param {number|undefined} rotation Rotation. + */ +ol.View2D.prototype.setRotation = function(rotation) { + this.set(ol.View2DProperty.ROTATION, rotation); +}; +goog.exportProperty( + ol.View2D.prototype, + 'setRotation', + ol.View2D.prototype.setRotation); + + +/** + * @param {ol.Map} map Map. + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + */ +ol.View2D.prototype.rotate = function(map, rotation, delta) { + rotation = this.constraints_.rotation(rotation, delta); + this.setRotation(rotation); +}; + + +/** + * @private + * @param {ol.Map} map Map. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + */ +ol.View2D.prototype.zoom_ = function(map, resolution, opt_anchor) { + if (goog.isDefAndNotNull(resolution) && goog.isDefAndNotNull(opt_anchor)) { + var anchor = opt_anchor; + var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); + var oldResolution = this.getResolution(); + var x = anchor.x - resolution * (anchor.x - oldCenter.x) / oldResolution; + var y = anchor.y - resolution * (anchor.y - oldCenter.y) / oldResolution; + var center = new ol.Coordinate(x, y); + map.withFrozenRendering(function() { + this.setCenter(center); + this.setResolution(resolution); + }, this); + } else { + this.setResolution(resolution); + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {number} delta Delta from previous zoom level. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + */ +ol.View2D.prototype.zoom = function(map, delta, opt_anchor) { + var resolution = this.constraints_.resolution(this.getResolution(), delta); + this.zoom_(map, resolution, opt_anchor); +}; + + +/** + * @param {ol.Map} map Map. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + */ +ol.View2D.prototype.zoomToResolution = function(map, resolution, opt_anchor) { + resolution = this.constraints_.resolution(resolution, 0); + this.zoom_(map, resolution, opt_anchor); +}; + + +/** + * @private + * @param {ol.View2DOptions} view2DOptions View2D options. + * @return {ol.Constraints} Constraints. + */ +ol.View2D.createConstraints_ = function(view2DOptions) { + var resolutionConstraint; + if (goog.isDef(view2DOptions.resolutions)) { + resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions( + view2DOptions.resolutions); + } else { + var maxResolution, numZoomLevels, zoomFactor; + if (goog.isDef(view2DOptions.maxResolution) && + goog.isDef(view2DOptions.numZoomLevels) && + goog.isDef(view2DOptions.zoomFactor)) { + maxResolution = view2DOptions.maxResolution; + numZoomLevels = view2DOptions.numZoomLevels; + zoomFactor = view2DOptions.zoomFactor; + } else { + var projectionExtent = ol.Projection.createProjection( + view2DOptions.projection, 'EPSG:3857').getExtent(); + maxResolution = Math.max( + projectionExtent.maxX - projectionExtent.minX, + projectionExtent.maxY - projectionExtent.minY) / 256; + // number of steps we want between two data resolutions + var numSteps = 4; + numZoomLevels = 29 * numSteps; + zoomFactor = Math.exp(Math.log(2) / numSteps); + } + resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( + zoomFactor, maxResolution, numZoomLevels - 1); + } + // FIXME rotation constraint is not configurable at the moment + var rotationConstraint = ol.RotationConstraint.none; + return new ol.Constraints(resolutionConstraint, rotationConstraint); +}; diff --git a/test/ol.html b/test/ol.html index 08793ee015..9c00d22ecd 100644 --- a/test/ol.html +++ b/test/ol.html @@ -78,6 +78,7 @@ + diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index 1ce6d9f694..b30207e771 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -4,6 +4,7 @@ goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Map'); goog.require('ol.RendererHint'); +goog.require('ol.View2D'); goog.require('ol.layer.TileLayer'); goog.require('ol.source.XYZ'); @@ -24,56 +25,6 @@ describe('ol.Map', function() { }); }); - describe('create constraints', function() { - - describe('create resolution constraint', function() { - - describe('with no options', function() { - it('gives a correct resolution constraint function', function() { - var options = {}; - var fn = ol.Map.createConstraints_(options).resolution; - expect(fn(156543.03392804097, 0)) - .toRoughlyEqual(156543.03392804097, 1e-9); - expect(fn(78271.51696402048, 0)) - .toRoughlyEqual(78271.51696402048, 1e-10); - }); - }); - - describe('with maxResolution, numZoomLevels, and zoomFactor options', - function() { - it('gives a correct resolution constraint function', function() { - var options = { - maxResolution: 81, - numZoomLevels: 4, - zoomFactor: 3 - }; - var fn = ol.Map.createConstraints_(options).resolution; - expect(fn(82, 0)).toEqual(81); - expect(fn(81, 0)).toEqual(81); - expect(fn(27, 0)).toEqual(27); - expect(fn(9, 0)).toEqual(9); - expect(fn(3, 0)).toEqual(3); - expect(fn(2, 0)).toEqual(3); - }); - }); - - describe('with resolutions', function() { - it('gives a correct resolution constraint function', function() { - var options = { - resolutions: [97, 76, 65, 54, 0.45] - }; - var fn = ol.Map.createConstraints_(options).resolution; - expect(fn(97, 0), 97); - expect(fn(76, 0), 76); - expect(fn(65, 0), 65); - expect(fn(54, 0), 54); - expect(fn(0.45, 0), 0.45); - }); - }); - - }); - }); - describe('create interactions', function() { var options; @@ -158,11 +109,13 @@ describe('ol.Map', function() { }); map = new ol.Map({ - center: new ol.Coordinate(0, 0), layers: new ol.Collection([layer]), renderer: ol.RendererHint.DOM, target: 'map', - zoom: 1 + view: new ol.View2D({ + center: new ol.Coordinate(0, 0), + zoom: 1 + }) }); }); @@ -183,7 +136,7 @@ describe('ol.Map', function() { var duration = 500; var destination = new ol.Coordinate(1000, 1000); - var origin = map.getCenter(); + var origin = map.getView().getCenter(); var start = new Date().getTime(); var x0 = origin.x; var y0 = origin.y; @@ -202,7 +155,7 @@ describe('ol.Map', function() { x = destination.x; y = destination.y; } - map.setCenter(new ol.Coordinate(x, y)); + map.getView().setCenter(new ol.Coordinate(x, y)); if (more) { animationDelay.start(); } @@ -220,7 +173,7 @@ describe('ol.Map', function() { waits(100); runs(function() { expect(o.callback).toHaveBeenCalled(); - var loc = map.getCenter(); + var loc = map.getView().getCenter(); expect(loc.x).not.toEqual(origin.x); expect(loc.y).not.toEqual(origin.y); expect(loc.x).not.toEqual(destination.x); @@ -230,7 +183,7 @@ describe('ol.Map', function() { // confirm that the map has reached the destination after the duration waits(duration); runs(function() { - var loc = map.getCenter(); + var loc = map.getView().getCenter(); expect(loc.x).toEqual(destination.x); expect(loc.y).toEqual(destination.y); }); diff --git a/test/spec/ol/object.test.js b/test/spec/ol/object.test.js index 8cfbc2c196..d445ac0e39 100644 --- a/test/spec/ol/object.test.js +++ b/test/spec/ol/object.test.js @@ -46,16 +46,19 @@ describe('ol.Object', function() { describe('notify', function() { - var listener1, listener2; + var listener1, listener2, listener3; beforeEach(function() { listener1 = jasmine.createSpy(); goog.events.listen(o, 'k_changed', listener1); + listener2 = jasmine.createSpy(); + goog.events.listen(o, 'changed', listener2); + var o2 = new ol.Object(); o2.bindTo('k', o); - listener2 = jasmine.createSpy(); - goog.events.listen(o2, 'k_changed', listener2); + listener3 = jasmine.createSpy(); + goog.events.listen(o2, 'k_changed', listener3); }); it('dispatches events', function() { @@ -63,24 +66,32 @@ describe('ol.Object', function() { expect(listener1).toHaveBeenCalled(); }); - it('dispatches events to bound objects', function() { + it('dispatches generic change events to bound objects', function() { o.notify('k'); expect(listener2).toHaveBeenCalled(); }); + + it('dispatches events to bound objects', function() { + o.notify('k'); + expect(listener3).toHaveBeenCalled(); + }); }); describe('set', function() { - var listener1, o2, listener2; + var listener1, o2, listener2, listener3; beforeEach(function() { listener1 = jasmine.createSpy(); goog.events.listen(o, 'k_changed', listener1); + listener2 = jasmine.createSpy(); + goog.events.listen(o, 'changed', listener2); + o2 = new ol.Object(); o2.bindTo('k', o); - listener2 = jasmine.createSpy(); - goog.events.listen(o2, 'k_changed', listener2); + listener3 = jasmine.createSpy(); + goog.events.listen(o2, 'k_changed', listener3); }); it('dispatches events to object', function() { @@ -88,15 +99,25 @@ describe('ol.Object', function() { expect(listener1).toHaveBeenCalled(); }); - it('dispatches events to bound object', function() { + it('dispatches generic change events to object', function() { o.set('k', 1); expect(listener2).toHaveBeenCalled(); }); + it('dispatches events to bound object', function() { + o.set('k', 1); + expect(listener3).toHaveBeenCalled(); + }); + it('dispatches events to object bound to', function() { o2.set('k', 2); expect(listener1).toHaveBeenCalled(); }); + + it('dispatches generic change events to object bound to', function() { + o2.set('k', 2); + expect(listener2).toHaveBeenCalled(); + }); }); describe('bind', function() { diff --git a/test/spec/ol/tilerange.test.js b/test/spec/ol/tilerange.test.js index b9eb37f465..a772a59f16 100644 --- a/test/spec/ol/tilerange.test.js +++ b/test/spec/ol/tilerange.test.js @@ -51,36 +51,6 @@ describe('ol.TileRange', function() { }); }); - describe('forEachTileCoord', function() { - it('iterates as expected', function() { - var tileRange = new ol.TileRange(0, 2, 1, 3); - - var tileCoords = []; - tileRange.forEachTileCoord(5, function(tileCoord) { - tileCoords.push( - new ol.TileCoord(tileCoord.z, tileCoord.x, tileCoord.y)); - }); - - expect(tileCoords.length).toEqual(4); - - expect(tileCoords[0].z).toEqual(5); - expect(tileCoords[0].x).toEqual(0); - expect(tileCoords[0].y).toEqual(2); - - expect(tileCoords[1].z).toEqual(5); - expect(tileCoords[1].x).toEqual(0); - expect(tileCoords[1].y).toEqual(3); - - expect(tileCoords[2].z).toEqual(5); - expect(tileCoords[2].x).toEqual(1); - expect(tileCoords[2].y).toEqual(2); - - expect(tileCoords[3].z).toEqual(5); - expect(tileCoords[3].x).toEqual(1); - expect(tileCoords[3].y).toEqual(3); - }); - }); - describe('getSize', function() { it('returns the expected size', function() { var tileRange = new ol.TileRange(0, 1, 2, 4); diff --git a/test/spec/ol/view2d.test.js b/test/spec/ol/view2d.test.js new file mode 100644 index 0000000000..c4ea5ad57a --- /dev/null +++ b/test/spec/ol/view2d.test.js @@ -0,0 +1,53 @@ +goog.require('ol.View2D'); + +describe('ol.View2D', function() { + describe('create constraints', function() { + + describe('create resolution constraint', function() { + + describe('with no options', function() { + it('gives a correct resolution constraint function', function() { + var options = {}; + var fn = ol.View2D.createConstraints_(options).resolution; + expect(fn(156543.03392804097, 0)) + .toRoughlyEqual(156543.03392804097, 1e-9); + expect(fn(78271.51696402048, 0)) + .toRoughlyEqual(78271.51696402048, 1e-10); + }); + }); + + describe('with maxResolution, numZoomLevels, and zoomFactor options', + function() { + it('gives a correct resolution constraint function', function() { + var options = { + maxResolution: 81, + numZoomLevels: 4, + zoomFactor: 3 + }; + var fn = ol.View2D.createConstraints_(options).resolution; + expect(fn(82, 0)).toEqual(81); + expect(fn(81, 0)).toEqual(81); + expect(fn(27, 0)).toEqual(27); + expect(fn(9, 0)).toEqual(9); + expect(fn(3, 0)).toEqual(3); + expect(fn(2, 0)).toEqual(3); + }); + }); + + describe('with resolutions', function() { + it('gives a correct resolution constraint function', function() { + var options = { + resolutions: [97, 76, 65, 54, 0.45] + }; + var fn = ol.View2D.createConstraints_(options).resolution; + expect(fn(97, 0)).toEqual(97); + expect(fn(76, 0)).toEqual(76); + expect(fn(65, 0)).toEqual(65); + expect(fn(54, 0)).toEqual(54); + expect(fn(0.45, 0)).toEqual(0.45); + }); + }); + + }); + }); +});