Skip to content

Instantly share code, notes, and snippets.

@tschaub
Created July 18, 2012 15:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tschaub/3136773 to your computer and use it in GitHub Desktop.
Save tschaub/3136773 to your computer and use it in GitHub Desktop.

API

Firstly, without any HPI helper functions, the API currently looks like this:

    var map = new ol.Map(document.getElementById('map'));
    var layer = ol.Layer.createOpenStreetMap({opacity: 0.5});
    map.getLayers().push(layer);

Adding a few flexibly-typed helper functions and some combined getter/setter methods will shorten this further.

ARCHITECTURE

Inheritance from goog.events.EventTarget

Most major classes directly or indirectly inherit from goog.events.EventTarget. This allows events to be used natively through the codebase as an alternative to callbacks, inline with what Eric and Andreas have been working on.

ol.Object and ol.Array

I've added a couple of base classes for objects and collections of objects modeled on MVCObject and MVCArray in Google Maps. This provides several advantages, including automated firing of events when properties change, automated setup and teardown of event listeners, and binding of property values between multiple objects. This will also ease the addition of combined getter/setter methods.

goog.math.Coordinate, goog.math.Box, and goog.math.Size

I've used classes and functions from the Closure library as base types where possible to avoid duplicating code. There's already a lot of rich functionality available. One pain point is that bounds and extents now have properties called top/right/bottom/left like CSS rather than minX/minY/maxX/maxY.

Bounds and extents

"Bounds" consistently refers to a two dimensional range of multiple objects, e.g. the range of tiles required to cover the visible part of the map. An "Extent" is the geographic range of a single object, e.g. the extent of the visible map or the extent of a projection's validity. These terms are used consistently though the code and are enforced with the Closure type checker (although they inherit from a common base class to share code). There is a special ol.TileBounds class for representing bounds of tiles in integer tile coordinates.

Extents are now always unreferenced, and ol.TileBounds do not include z-coordinates. It is assumed that the projection/z-coordinate information is already available if it is required. This can be revisited if needed.

ol.Projection

I've re-thought the architecture of ol.Projection to work better with the Closure compiler, reduce the number of objects, and increase performance. You should never create a new ol.Projection object now. Use ol.Projection.createFromCode(string) factory function instead. This will return an instance, effectively a singleton. If you wish to transform more than one point, consider using ol.Projection.getTransform to get a reference to the transformation function directly: this does the lookup only once. I also cleaned up the forward and inverse spherical Mercator code to make it more accurate.

ol.TileGrid

The structure of how tiles are laid out and converted to/from geographic coordinates is now in a single ol.TileGrid class. OSM-style tile grids can be created with a single call to the ol.TileGrid.createOpenStreetMap(maxZoom) function. The class is flexible enough to cover the most complex WMTS layouts that I've seen, and avoids the need to create specialised subclasses.

Generating tile URLs

URL generation is now handled by functions with the type signature function(ol.TileCoord): string. There are helper functions to generate these from templates and to load balance across multiple tile servers.

ol.Store/ol.Layer distinction

I've separated data from presentation with the ol.Store/ol.Layer distinction. An ol.Store just contains methods for accessing geographical data. An ol.Layer is an ol.Store with view-related options such as visibility and opacity. Both ol.Layers and ol.Stores should be re-usable across multiple map instances.

To follow up on the ol.Layer / ol.Store discussion Tim, Eric and I have had:

ol.layer.osm() and ol.layer.wmts() would return an ol.Layer (or maybe an ol.TileLayer). The HPI could add functions to ol.Layer that would be proxied to the store. Ex:

    ol.Layer.prototype.url = function(opt_arg) {
        if (arguments.length == 1 && goog.isDef(opt_arg)) {
            this.getStore().setUrl(opt_arg);
            return this;
        } else {
            this.getStore().getUrl();
        }
    };

ol.TileStore

ol.TileStores consist of an ol.TileGrid, a function for generating tile URLs from tile coordinates, and a tile cache. Importantly, currently repeatedly requesting the tile for a given tile coordinate will return the same ol.Tile object each time. Currently the cache grows without limit, this will need to change.

ol.Camera abstraction

I've abstracted out ol.Camera to represent a point of view.

All renderers are composite

Both the DOM and the WebGL Map renderers are composite, i.e. they consist of multiple LayerRenderers. The functionality for composite renderers has been moved to ol.MapRenderer. Subclasses such as ol.DOMMapRenderer and ol.WebGLMapRenderer should override the createLayerRenderer(layer) method. MapRenderers are responsible for creating any DOM structure that they might need inside the target element.

No renderer-specific code in ol.Tile

Setting CSS styles and classes on tile images and other renderer-specific functionality is now entirely the responsibility of the renderers.

ol.Map

Currently, a map is little more than a target DOM element, a list of layers, and a camera. Internally it creates a MapRenderer object to actually render the layers to the target DOM element.

INFRASTRUCTURE

Unit testing

I've used the JSUnit framework shipped with the Closure Library rather than Jasmine simply because it's well supported by the Plovr build tool. Plovr automatically creates a list of tests by walking the src directory and generates HTML files to run them. Hopefully, sometime soon Plovr will support Jasmine in the same way, at which point we can port the JSUnit tests to Jasmine.

Linter

I've used the Closure Linter in strict mode, simply because it is very fast and effective tool for finding bugs in my code and helps ensure that the code is laid out consistently. One of the features of the style is a line length of 80 characters. Although many developers use 4-space indents, this does not work well with goog.closures.long.package.names.withLongDescriptiveNames - you quickly run out of columns and having to break lines.

All exp branch files are passed to the linter. Non-compliant files are filtered out using a blacklist. Plovr does not support the linter, and I don't have enough experience with ant to know how to filter out files with ant (although I'm sure that it's possible). So, the linter is run through make with the command 'make lint'.

Lowercase filenames

Having mixed case filenames on my Mac OS X machine with its case-insensitive but case-preserving file system was confusing git when files were added and removed (try git rm name.js ; git add NAME.js). Also, some files contain both namespaces and multiple classes and it seemed incorrect to name the file only after a single class. So, I switched to using all-lowercase filenames for ease and simplicity.

TO DO

This is still a snapshot at this stage. Although a lot of code has been written, it is only the more fundamental classes that have been tested. There will be bugs, both in implementation and in conception! The top of my to-do list looks like:

  • Validate the design by writing a map renderer and a layer renderer (Tim? :-) )
  • Investigate normalized tile coordinates as suggested by Tim
  • Experiment with binding and events to allow map and layer renderers to respond to changes in layers and in the camera.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment