Skip to content

Commit 24d0bb1

Browse files
authored
Merge pull request #616 from OpenGeoscience/annotaton-registry
Add an annotation registry.
2 parents c15bcee + e004ec0 commit 24d0bb1

File tree

8 files changed

+207
-9
lines changed

8 files changed

+207
-9
lines changed

examples/annotations/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ $(function () {
4646
// create an annotation layer
4747
layer = map.createLayer('annotation', {
4848
renderer: query.renderer ? (query.renderer === 'html' ? null : query.renderer) : undefined,
49-
features: query.renderer ? undefined : ['polygon', 'line', 'point']
49+
annotations: query.renderer ? undefined : geo.listAnnotations()
5050
});
5151
// bind to the mouse click and annotation mode events
5252
layer.geoOn(geo.event.mouseclick, mouseClickToStart);

src/annotation.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ var $ = require('jquery');
22
var inherit = require('./inherit');
33
var geo_event = require('./event');
44
var transform = require('./transform');
5+
var registerAnnotation = require('./registry').registerAnnotation;
6+
var lineFeature = require('./lineFeature');
7+
var pointFeature = require('./pointFeature');
8+
var polygonFeature = require('./polygonFeature');
59

610
var annotationId = 0;
711

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

317+
var rectangleRequiredFeatures = {};
318+
rectangleRequiredFeatures[polygonFeature.capabilities.feature] = true;
319+
registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);
320+
312321
/////////////////////////////////////////////////////////////////////////////
313322
/**
314323
* Polygon annotation class
@@ -504,6 +513,11 @@ var polygonAnnotation = function (args) {
504513
};
505514
inherit(polygonAnnotation, annotation);
506515

516+
var polygonRequiredFeatures = {};
517+
polygonRequiredFeatures[polygonFeature.capabilities.feature] = true;
518+
polygonRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
519+
registerAnnotation('polygon', polygonAnnotation, polygonRequiredFeatures);
520+
507521
/////////////////////////////////////////////////////////////////////////////
508522
/**
509523
* Point annotation class
@@ -598,6 +612,10 @@ var pointAnnotation = function (args) {
598612
};
599613
inherit(pointAnnotation, annotation);
600614

615+
var pointRequiredFeatures = {};
616+
pointRequiredFeatures[pointFeature.capabilities.feature] = true;
617+
registerAnnotation('point', pointAnnotation, pointRequiredFeatures);
618+
601619
module.exports = {
602620
state: annotationState,
603621
annotation: annotation,

src/layer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var sceneObject = require('./sceneObject');
33
var feature = require('./feature');
44
var checkRenderer = require('./registry').checkRenderer;
55
var rendererForFeatures = require('./registry').rendererForFeatures;
6+
var rendererForAnnotations = require('./registry').rendererForAnnotations;
67

78
//////////////////////////////////////////////////////////////////////////////
89
/**
@@ -45,7 +46,9 @@ var layer = function (arg) {
4546
m_canvas = null,
4647
m_renderer = null,
4748
m_initialized = false,
48-
m_rendererName = arg.renderer === undefined ? rendererForFeatures(arg.features) : arg.renderer,
49+
m_rendererName = arg.renderer !== undefined ? arg.renderer : (
50+
arg.annotations ? rendererForAnnotations(arg.annotations) :
51+
rendererForFeatures(arg.features)),
4952
m_dataTime = timestamp(),
5053
m_updateTime = timestamp(),
5154
m_sticky = arg.sticky === undefined ? true : arg.sticky,

src/lineFeature.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ lineFeature.create = function (layer, spec) {
260260
};
261261

262262
lineFeature.capabilities = {
263+
/* core feature name -- support in any manner */
264+
feature: 'line',
263265
/* support for solid-colored, constant-width lines */
264266
basic: 'line.basic',
265267
/* support for lines that vary in width and color */

src/pointFeature.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,5 +435,10 @@ pointFeature.create = function (layer, renderer, spec) {
435435
return feature.create(layer, spec);
436436
};
437437

438+
pointFeature.capabilities = {
439+
/* core feature name -- support in any manner */
440+
feature: 'point'
441+
};
442+
438443
inherit(pointFeature, feature);
439444
module.exports = pointFeature;

src/polygonFeature.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,5 +406,10 @@ polygonFeature.create = function (layer, spec) {
406406
return feature.create(layer, spec);
407407
};
408408

409+
polygonFeature.capabilities = {
410+
/* core feature name -- support in any manner */
411+
feature: 'polygon'
412+
};
413+
409414
inherit(polygonFeature, feature);
410415
module.exports = polygonFeature;

src/registry.js

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var features = {};
99
var featureCapabilities = {};
1010
var fileReaders = {};
1111
var rendererLayerAdjustments = {};
12+
var annotations = {};
1213
var util = {};
1314

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

179-
// TODO Add warning if the name already exists
182+
var old = features[category][name];
183+
if (old) {
184+
console.warn('The ' + category + '.' + name + ' feature is already registered');
185+
}
180186
features[category][name] = func;
181187
featureCapabilities[category][name] = capabilities;
188+
return old;
182189
};
183190

184191
//////////////////////////////////////////////////////////////////////////////
@@ -207,15 +214,22 @@ util.createFeature = function (name, layer, renderer, arg) {
207214
//////////////////////////////////////////////////////////////////////////////
208215
/**
209216
* Register a layer adjustment.
217+
*
218+
* @returns {object} if this layer adjustment replaces an existing one, this
219+
* was the value that was replaced. In this case, a warning is issued.
210220
*/
211221
//////////////////////////////////////////////////////////////////////////////
212222
util.registerLayerAdjustment = function (category, name, func) {
213223
if (!(category in rendererLayerAdjustments)) {
214224
rendererLayerAdjustments[category] = {};
215225
}
216226

217-
// TODO Add warning if the name already exists
227+
var old = rendererLayerAdjustments[category][name];
228+
if (old) {
229+
console.warn('The ' + category + '.' + name + ' layer adjustment is already registered');
230+
}
218231
rendererLayerAdjustments[category][name] = func;
232+
return old;
219233
};
220234

221235
//////////////////////////////////////////////////////////////////////////////
@@ -276,15 +290,22 @@ util.createLayer = function (name, map, arg) {
276290
//////////////////////////////////////////////////////////////////////////////
277291
/**
278292
* Register a new widget type
293+
*
294+
* @returns {object} if this widget replaces an existing one, this was the
295+
* value that was replaced. In this case, a warning is issued.
279296
*/
280297
//////////////////////////////////////////////////////////////////////////////
281298
util.registerWidget = function (category, name, func) {
282299
if (!(category in widgets)) {
283300
widgets[category] = {};
284301
}
285302

286-
// TODO Add warning if the name already exists
303+
var old = widgets[category][name];
304+
if (old) {
305+
console.warn('The ' + category + '.' + name + ' widget is already registered');
306+
}
287307
widgets[category][name] = func;
308+
return old;
288309
};
289310

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

332+
//////////////////////////////////////////////////////////////////////////////
333+
/**
334+
* Register a new annotation type
335+
*
336+
* @param {string} name The annotation name
337+
* @param {function} func A function to call to create the annotation.
338+
* @param {object|undefined} features A map of features that are used by this
339+
* annotation. Each key is a feature that is used. If the value is true,
340+
* the that feature is always needed. If a list, then it is the set of
341+
* annotation states for which that feature is required. These can be
342+
* used to pick an pparopriate renderer when creating an annotation layer.
343+
* @returns {object} if this annotation replaces an existing one, this was the
344+
* value that was replaced. In this case, a warning is issued.
345+
*/
346+
//////////////////////////////////////////////////////////////////////////////
347+
util.registerAnnotation = function (name, func, features) {
348+
var old = annotations[name];
349+
if (old) {
350+
console.warn('The ' + name + ' annotation is already registered');
351+
}
352+
annotations[name] = {func: func, features: features || {}};
353+
return old;
354+
};
355+
356+
//////////////////////////////////////////////////////////////////////////////
357+
/**
358+
* Get a list of registered annotation types.
359+
*
360+
* @return {array} a list of registered annotations.
361+
*/
362+
//////////////////////////////////////////////////////////////////////////////
363+
util.listAnnotations = function () {
364+
return Object.keys(annotations);
365+
};
366+
367+
//////////////////////////////////////////////////////////////////////////////
368+
/**
369+
* Get a list of required features for a set of annotations.
370+
*
371+
* @param {array|object|undefined} annotationList A list of annotations that
372+
* will be used. Instead of a list, if this is an object, the keys are the
373+
* annotation names, and the values are each a list of modes that will be
374+
* used with that annotation. For example, ['polygon', 'rectangle'] lists
375+
* features required to show those annotations in any mode, whereas
376+
* {polygon: [annotationState.done], rectangle: [annotationState.done]} only
377+
* lists features thatre are needed to show the completed annotations.
378+
* @return {array} a list of features needed for the specified annotations.
379+
* There may be duplicates in the list.
380+
*/
381+
//////////////////////////////////////////////////////////////////////////////
382+
util.featuresForAnnotations = function (annotationList) {
383+
var features = [];
384+
385+
var annList = Array.isArray(annotationList) ? annotationList : Object.keys(annotationList);
386+
annList.forEach(function (ann) {
387+
if (!annotations[ann]) {
388+
return;
389+
}
390+
Object.keys(annotations[ann].features).forEach(function (feature) {
391+
if (Array.isArray(annotationList) || annotationList[ann] === true ||
392+
!Array.isArray(annotations[ann].features[feature])) {
393+
features.push(feature);
394+
} else {
395+
annotationList[ann].forEach(function (state) {
396+
if ($.inArray(state, annotations[ann].features[feature]) >= 0) {
397+
features.push(feature);
398+
}
399+
});
400+
}
401+
});
402+
});
403+
return features;
404+
};
405+
406+
//////////////////////////////////////////////////////////////////////////////
407+
/**
408+
* Check if there is a renderer that is supported and supports a list of
409+
* annotations. If not, display a warning. This generates a list of required
410+
* features, then picks the first renderer that supports all of thse features.
411+
*
412+
* @param {array|object|undefined} annotationList A list of annotations that
413+
* will be used with this renderer. Instead of a list, if this is an object,
414+
* the keys are the annotation names, and the values are each a list of modes
415+
* that will be used with that annotation. See featuresForAnnotations for
416+
* more details.
417+
* @return {string|null|false} the name of the renderer that should be used or
418+
* false if no valid renderer can be determined.
419+
*/
420+
//////////////////////////////////////////////////////////////////////////////
421+
util.rendererForAnnotations = function (annotationList) {
422+
return util.rendererForFeatures(util.featuresForAnnotations(annotationList));
423+
};
424+
311425
module.exports = util;

tests/cases/annotation.js

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('geo.annotation', function () {
4444
expect(ann.geojson()).toBe('not implemented');
4545
map = create_map();
4646
layer = map.createLayer('annotation', {
47-
features: ['polygon', 'line', 'point']
47+
annotations: geo.listAnnotations()
4848
});
4949
ann = geo.annotation.annotation('test2', {
5050
layer: layer,
@@ -198,7 +198,7 @@ describe('geo.annotation', function () {
198198
it('mouseClick', function () {
199199
var map = create_map();
200200
var layer = map.createLayer('annotation', {
201-
features: ['polygon', 'line']
201+
annotations: ['polygon']
202202
});
203203
var ann = geo.annotation.polygonAnnotation({layer: layer});
204204
var time = new Date().getTime();
@@ -303,4 +303,55 @@ describe('geo.annotation', function () {
303303
expect(ann.state()).toBe(geo.annotation.state.done);
304304
});
305305
});
306+
307+
describe('annotation registry', function () {
308+
it('listAnnotations', function () {
309+
var list = geo.listAnnotations();
310+
expect($.inArray('rectangle', list) >= 0).toBe(true);
311+
expect($.inArray('polygon', list) >= 0).toBe(true);
312+
expect($.inArray('point', list) >= 0).toBe(true);
313+
expect($.inArray('unknown', list) >= 0).toBe(false);
314+
});
315+
it('registerAnnotation', function () {
316+
var func = function () {};
317+
expect($.inArray('newshape', geo.listAnnotations()) >= 0).toBe(false);
318+
expect(geo.registerAnnotation('newshape', func)).toBe(undefined);
319+
expect($.inArray('newshape', geo.listAnnotations()) >= 0).toBe(true);
320+
expect(geo.registerAnnotation('newshape', func).func).toBe(func);
321+
expect($.inArray('newshape', geo.listAnnotations()) >= 0).toBe(true);
322+
});
323+
it('featuresForAnnotations', function () {
324+
var features = geo.featuresForAnnotations(['polygon']);
325+
expect($.inArray('polygon', features) >= 0).toBe(true);
326+
expect($.inArray('line.basic', features) >= 0).toBe(true);
327+
expect($.inArray('point', features) >= 0).toBe(false);
328+
features = geo.featuresForAnnotations({polygon: true});
329+
expect($.inArray('polygon', features) >= 0).toBe(true);
330+
expect($.inArray('line.basic', features) >= 0).toBe(true);
331+
expect($.inArray('point', features) >= 0).toBe(false);
332+
features = geo.featuresForAnnotations({polygon: [geo.annotation.state.done]});
333+
expect($.inArray('polygon', features) >= 0).toBe(true);
334+
expect($.inArray('line.basic', features) >= 0).toBe(false);
335+
expect($.inArray('point', features) >= 0).toBe(false);
336+
features = geo.featuresForAnnotations({polygon: [geo.annotation.state.done, geo.annotation.state.create]});
337+
expect($.inArray('polygon', features) >= 0).toBe(true);
338+
expect($.inArray('line.basic', features) >= 0).toBe(true);
339+
expect($.inArray('point', features) >= 0).toBe(false);
340+
features = geo.featuresForAnnotations(['polygon', 'point']);
341+
expect($.inArray('polygon', features) >= 0).toBe(true);
342+
expect($.inArray('line.basic', features) >= 0).toBe(true);
343+
expect($.inArray('point', features) >= 0).toBe(true);
344+
features = geo.featuresForAnnotations(['polygon', 'unknown']);
345+
expect($.inArray('polygon', features) >= 0).toBe(true);
346+
expect($.inArray('line.basic', features) >= 0).toBe(true);
347+
expect($.inArray('point', features) >= 0).toBe(false);
348+
});
349+
it('rendererForAnnotations', function () {
350+
expect(geo.rendererForAnnotations(['polygon'])).toBe('vgl');
351+
expect(geo.rendererForAnnotations(['point'])).toBe('vgl');
352+
geo.gl.vglRenderer.supported = function () { return false; };
353+
expect(geo.rendererForAnnotations(['polygon'])).toBe(false);
354+
expect(geo.rendererForAnnotations(['point'])).toBe('d3');
355+
});
356+
});
306357
});

0 commit comments

Comments
 (0)