Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an annotation registry. #616

Merged
merged 2 commits into from
Sep 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
});
});
});