Skip to content

Commit

Permalink
Merge pull request #616 from OpenGeoscience/annotaton-registry
Browse files Browse the repository at this point in the history
Add an annotation registry.
  • Loading branch information
manthey authored Sep 28, 2016
2 parents c15bcee + e004ec0 commit 24d0bb1
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 9 deletions.
2 changes: 1 addition & 1 deletion examples/annotations/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ $(function () {
// create an annotation layer
layer = map.createLayer('annotation', {
renderer: query.renderer ? (query.renderer === 'html' ? null : query.renderer) : undefined,
features: query.renderer ? undefined : ['polygon', 'line', 'point']
annotations: query.renderer ? undefined : geo.listAnnotations()
});
// bind to the mouse click and annotation mode events
layer.geoOn(geo.event.mouseclick, mouseClickToStart);
Expand Down
20 changes: 19 additions & 1 deletion src/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ var $ = require('jquery');
var inherit = require('./inherit');
var geo_event = require('./event');
var transform = require('./transform');
var registerAnnotation = require('./registry').registerAnnotation;
var lineFeature = require('./lineFeature');
var pointFeature = require('./pointFeature');
var polygonFeature = require('./polygonFeature');

var annotationId = 0;

Expand All @@ -16,7 +20,8 @@ var annotationState = {
* Base annotation class
*
* @class geo.annotation
* @param {string} type the type of annotation.
* @param {string} type the type of annotation. These should be registered
* with utils.registerAnnotation and can be listed with same function.
* @param {object?} options Inidividual annotations have additional options.
* @param {string} [options.name] A name for the annotation. This defaults to
* the type with a unique ID suffixed to it.
Expand Down Expand Up @@ -309,6 +314,10 @@ var rectangleAnnotation = function (args) {
};
inherit(rectangleAnnotation, annotation);

var rectangleRequiredFeatures = {};
rectangleRequiredFeatures[polygonFeature.capabilities.feature] = true;
registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);

/////////////////////////////////////////////////////////////////////////////
/**
* Polygon annotation class
Expand Down Expand Up @@ -504,6 +513,11 @@ var polygonAnnotation = function (args) {
};
inherit(polygonAnnotation, annotation);

var polygonRequiredFeatures = {};
polygonRequiredFeatures[polygonFeature.capabilities.feature] = true;
polygonRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
registerAnnotation('polygon', polygonAnnotation, polygonRequiredFeatures);

/////////////////////////////////////////////////////////////////////////////
/**
* Point annotation class
Expand Down Expand Up @@ -598,6 +612,10 @@ var pointAnnotation = function (args) {
};
inherit(pointAnnotation, annotation);

var pointRequiredFeatures = {};
pointRequiredFeatures[pointFeature.capabilities.feature] = true;
registerAnnotation('point', pointAnnotation, pointRequiredFeatures);

module.exports = {
state: annotationState,
annotation: annotation,
Expand Down
5 changes: 4 additions & 1 deletion src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var sceneObject = require('./sceneObject');
var feature = require('./feature');
var checkRenderer = require('./registry').checkRenderer;
var rendererForFeatures = require('./registry').rendererForFeatures;
var rendererForAnnotations = require('./registry').rendererForAnnotations;

//////////////////////////////////////////////////////////////////////////////
/**
Expand Down Expand Up @@ -45,7 +46,9 @@ var layer = function (arg) {
m_canvas = null,
m_renderer = null,
m_initialized = false,
m_rendererName = arg.renderer === undefined ? rendererForFeatures(arg.features) : arg.renderer,
m_rendererName = arg.renderer !== undefined ? arg.renderer : (
arg.annotations ? rendererForAnnotations(arg.annotations) :
rendererForFeatures(arg.features)),
m_dataTime = timestamp(),
m_updateTime = timestamp(),
m_sticky = arg.sticky === undefined ? true : arg.sticky,
Expand Down
2 changes: 2 additions & 0 deletions src/lineFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ lineFeature.create = function (layer, spec) {
};

lineFeature.capabilities = {
/* core feature name -- support in any manner */
feature: 'line',
/* support for solid-colored, constant-width lines */
basic: 'line.basic',
/* support for lines that vary in width and color */
Expand Down
5 changes: 5 additions & 0 deletions src/pointFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,10 @@ pointFeature.create = function (layer, renderer, spec) {
return feature.create(layer, spec);
};

pointFeature.capabilities = {
/* core feature name -- support in any manner */
feature: 'point'
};

inherit(pointFeature, feature);
module.exports = pointFeature;
5 changes: 5 additions & 0 deletions src/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,10 @@ polygonFeature.create = function (layer, spec) {
return feature.create(layer, spec);
};

polygonFeature.capabilities = {
/* core feature name -- support in any manner */
feature: 'polygon'
};

inherit(polygonFeature, feature);
module.exports = polygonFeature;
122 changes: 118 additions & 4 deletions src/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var features = {};
var featureCapabilities = {};
var fileReaders = {};
var rendererLayerAdjustments = {};
var annotations = {};
var util = {};

//////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -65,7 +66,7 @@ util.createRenderer = function (name, layer, canvas, options) {
* that would support those features.
*
* @params {string|null} name name of the desired renderer
* @params {boolean} noFallack if true, don't recommend a fallback
* @params {boolean} noFallback if true, don't recommend a fallback
* @return {string|null|false} the name of the renderer that should be used
* or false if no valid renderer can be determined.
*/
Expand Down Expand Up @@ -168,6 +169,8 @@ util.rendererForFeatures = function (featureList) {
* and image quads that support full transformations. The capabailities
* should be defined in the base feature in a capabilities object so that
* they can be referenced by that rather than an explicit string.
* @returns {object} if this feature replaces an existing one, this was the
* feature that was replaced. In this case, a warning is issued.
*/
//////////////////////////////////////////////////////////////////////////////
util.registerFeature = function (category, name, func, capabilities) {
Expand All @@ -176,9 +179,13 @@ util.registerFeature = function (category, name, func, capabilities) {
featureCapabilities[category] = {};
}

// TODO Add warning if the name already exists
var old = features[category][name];
if (old) {
console.warn('The ' + category + '.' + name + ' feature is already registered');
}
features[category][name] = func;
featureCapabilities[category][name] = capabilities;
return old;
};

//////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -207,15 +214,22 @@ util.createFeature = function (name, layer, renderer, arg) {
//////////////////////////////////////////////////////////////////////////////
/**
* Register a layer adjustment.
*
* @returns {object} if this layer adjustment replaces an existing one, this
* was the value that was replaced. In this case, a warning is issued.
*/
//////////////////////////////////////////////////////////////////////////////
util.registerLayerAdjustment = function (category, name, func) {
if (!(category in rendererLayerAdjustments)) {
rendererLayerAdjustments[category] = {};
}

// TODO Add warning if the name already exists
var old = rendererLayerAdjustments[category][name];
if (old) {
console.warn('The ' + category + '.' + name + ' layer adjustment is already registered');
}
rendererLayerAdjustments[category][name] = func;
return old;
};

//////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -276,15 +290,22 @@ util.createLayer = function (name, map, arg) {
//////////////////////////////////////////////////////////////////////////////
/**
* Register a new widget type
*
* @returns {object} if this widget replaces an existing one, this was the
* value that was replaced. In this case, a warning is issued.
*/
//////////////////////////////////////////////////////////////////////////////
util.registerWidget = function (category, name, func) {
if (!(category in widgets)) {
widgets[category] = {};
}

// TODO Add warning if the name already exists
var old = widgets[category][name];
if (old) {
console.warn('The ' + category + '.' + name + ' widget is already registered');
}
widgets[category][name] = func;
return old;
};

//////////////////////////////////////////////////////////////////////////////
Expand All @@ -308,4 +329,97 @@ util.createWidget = function (name, layer, arg) {
throw new Error('Cannot create unknown widget ' + name);
};

//////////////////////////////////////////////////////////////////////////////
/**
* Register a new annotation type
*
* @param {string} name The annotation name
* @param {function} func A function to call to create the annotation.
* @param {object|undefined} features A map of features that are used by this
* annotation. Each key is a feature that is used. If the value is true,
* the that feature is always needed. If a list, then it is the set of
* annotation states for which that feature is required. These can be
* used to pick an pparopriate renderer when creating an annotation layer.
* @returns {object} if this annotation replaces an existing one, this was the
* value that was replaced. In this case, a warning is issued.
*/
//////////////////////////////////////////////////////////////////////////////
util.registerAnnotation = function (name, func, features) {
var old = annotations[name];
if (old) {
console.warn('The ' + name + ' annotation is already registered');
}
annotations[name] = {func: func, features: features || {}};
return old;
};

//////////////////////////////////////////////////////////////////////////////
/**
* Get a list of registered annotation types.
*
* @return {array} a list of registered annotations.
*/
//////////////////////////////////////////////////////////////////////////////
util.listAnnotations = function () {
return Object.keys(annotations);
};

//////////////////////////////////////////////////////////////////////////////
/**
* Get a list of required features for a set of annotations.
*
* @param {array|object|undefined} annotationList A list of annotations that
* will be used. Instead of a list, if this is an object, the keys are the
* annotation names, and the values are each a list of modes that will be
* used with that annotation. For example, ['polygon', 'rectangle'] lists
* features required to show those annotations in any mode, whereas
* {polygon: [annotationState.done], rectangle: [annotationState.done]} only
* lists features thatre are needed to show the completed annotations.
* @return {array} a list of features needed for the specified annotations.
* There may be duplicates in the list.
*/
//////////////////////////////////////////////////////////////////////////////
util.featuresForAnnotations = function (annotationList) {
var features = [];

var annList = Array.isArray(annotationList) ? annotationList : Object.keys(annotationList);
annList.forEach(function (ann) {
if (!annotations[ann]) {
return;
}
Object.keys(annotations[ann].features).forEach(function (feature) {
if (Array.isArray(annotationList) || annotationList[ann] === true ||
!Array.isArray(annotations[ann].features[feature])) {
features.push(feature);
} else {
annotationList[ann].forEach(function (state) {
if ($.inArray(state, annotations[ann].features[feature]) >= 0) {
features.push(feature);
}
});
}
});
});
return features;
};

//////////////////////////////////////////////////////////////////////////////
/**
* Check if there is a renderer that is supported and supports a list of
* annotations. If not, display a warning. This generates a list of required
* features, then picks the first renderer that supports all of thse features.
*
* @param {array|object|undefined} annotationList A list of annotations that
* will be used with this renderer. Instead of a list, if this is an object,
* the keys are the annotation names, and the values are each a list of modes
* that will be used with that annotation. See featuresForAnnotations for
* more details.
* @return {string|null|false} the name of the renderer that should be used or
* false if no valid renderer can be determined.
*/
//////////////////////////////////////////////////////////////////////////////
util.rendererForAnnotations = function (annotationList) {
return util.rendererForFeatures(util.featuresForAnnotations(annotationList));
};

module.exports = util;
55 changes: 53 additions & 2 deletions tests/cases/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('geo.annotation', function () {
expect(ann.geojson()).toBe('not implemented');
map = create_map();
layer = map.createLayer('annotation', {
features: ['polygon', 'line', 'point']
annotations: geo.listAnnotations()
});
ann = geo.annotation.annotation('test2', {
layer: layer,
Expand Down Expand Up @@ -198,7 +198,7 @@ describe('geo.annotation', function () {
it('mouseClick', function () {
var map = create_map();
var layer = map.createLayer('annotation', {
features: ['polygon', 'line']
annotations: ['polygon']
});
var ann = geo.annotation.polygonAnnotation({layer: layer});
var time = new Date().getTime();
Expand Down Expand Up @@ -303,4 +303,55 @@ describe('geo.annotation', function () {
expect(ann.state()).toBe(geo.annotation.state.done);
});
});

describe('annotation registry', function () {
it('listAnnotations', function () {
var list = geo.listAnnotations();
expect($.inArray('rectangle', list) >= 0).toBe(true);
expect($.inArray('polygon', list) >= 0).toBe(true);
expect($.inArray('point', list) >= 0).toBe(true);
expect($.inArray('unknown', list) >= 0).toBe(false);
});
it('registerAnnotation', function () {
var func = function () {};
expect($.inArray('newshape', geo.listAnnotations()) >= 0).toBe(false);
expect(geo.registerAnnotation('newshape', func)).toBe(undefined);
expect($.inArray('newshape', geo.listAnnotations()) >= 0).toBe(true);
expect(geo.registerAnnotation('newshape', func).func).toBe(func);
expect($.inArray('newshape', geo.listAnnotations()) >= 0).toBe(true);
});
it('featuresForAnnotations', function () {
var features = geo.featuresForAnnotations(['polygon']);
expect($.inArray('polygon', features) >= 0).toBe(true);
expect($.inArray('line.basic', features) >= 0).toBe(true);
expect($.inArray('point', features) >= 0).toBe(false);
features = geo.featuresForAnnotations({polygon: true});
expect($.inArray('polygon', features) >= 0).toBe(true);
expect($.inArray('line.basic', features) >= 0).toBe(true);
expect($.inArray('point', features) >= 0).toBe(false);
features = geo.featuresForAnnotations({polygon: [geo.annotation.state.done]});
expect($.inArray('polygon', features) >= 0).toBe(true);
expect($.inArray('line.basic', features) >= 0).toBe(false);
expect($.inArray('point', features) >= 0).toBe(false);
features = geo.featuresForAnnotations({polygon: [geo.annotation.state.done, geo.annotation.state.create]});
expect($.inArray('polygon', features) >= 0).toBe(true);
expect($.inArray('line.basic', features) >= 0).toBe(true);
expect($.inArray('point', features) >= 0).toBe(false);
features = geo.featuresForAnnotations(['polygon', 'point']);
expect($.inArray('polygon', features) >= 0).toBe(true);
expect($.inArray('line.basic', features) >= 0).toBe(true);
expect($.inArray('point', features) >= 0).toBe(true);
features = geo.featuresForAnnotations(['polygon', 'unknown']);
expect($.inArray('polygon', features) >= 0).toBe(true);
expect($.inArray('line.basic', features) >= 0).toBe(true);
expect($.inArray('point', features) >= 0).toBe(false);
});
it('rendererForAnnotations', function () {
expect(geo.rendererForAnnotations(['polygon'])).toBe('vgl');
expect(geo.rendererForAnnotations(['point'])).toBe('vgl');
geo.gl.vglRenderer.supported = function () { return false; };
expect(geo.rendererForAnnotations(['polygon'])).toBe(false);
expect(geo.rendererForAnnotations(['point'])).toBe('d3');
});
});
});

0 comments on commit 24d0bb1

Please sign in to comment.