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

Allow point annotations to scale with zoom. #628

Merged
merged 3 commits into from
Oct 20, 2016
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions examples/annotations/index.jade
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ block append mainContent
.form-group(annotation-types='point')
label(for='edit-radius') Radius
input#edit-radius(option='radius', format='positive')
.form-group(annotation-types='point', title='Set to "false" to disable, "true" to use the specified radius at the current zoom, or a zoom level to use the specified radius at that zoom level.')
label(for='edit-scaled') Scale with Zoom
input#edit-radius(option='scaled', format='booleanOrNumber')
.form-group(annotation-types='point polygon rectangle')
label(for='edit-fill') Fill
select#edit-stroke(option='fill', format='boolean')
Expand Down
Binary file added examples/annotations/thumb.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 30 additions & 4 deletions src/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -690,20 +690,27 @@ registerAnnotation('polygon', polygonAnnotation, polygonRequiredFeatures);
* May specify:
* style.
* radius, fill, fillColor, fillOpacity, stroke, strokeWidth, strokeColor,
* strokeOpacity
* strokeOpacity, scaled
*
* If scaled is false, the point is not scaled with zoom level. If it is true,
* the radius is based on the zoom level at first instantiation. Otherwise, if
* it is a number, the radius is used at that zoom level.
*/
/////////////////////////////////////////////////////////////////////////////
var pointAnnotation = function (args) {
'use strict';
if (!(this instanceof pointAnnotation)) {
return new pointAnnotation(args);
}
var m_this = this;

args = $.extend(true, {}, {
style: {
fill: true,
fillColor: {r: 0, g: 1, b: 0},
fillOpacity: 0.25,
radius: 10,
scaled: false,
stroke: true,
strokeColor: {r: 0, g: 0, b: 0},
strokeOpacity: 1,
Expand All @@ -722,17 +729,36 @@ var pointAnnotation = function (args) {
this.features = function () {
var opt = this.options(),
state = this.state(),
features;
features, style, scaleOnZoom;
switch (state) {
case annotationState.create:
features = [];
break;
default:
style = opt.style;
if (opt.style.scaled || opt.style.scaled === 0) {
if (opt.style.scaled === true) {
opt.style.scaled = this.layer().map().zoom();
}
style = $.extend({}, style, {
radius: function () {
var radius = opt.style.radius,
zoom = m_this.layer().map().zoom();
if (util.isFunction(radius)) {
radius = radius.apply(this, arguments);
}
radius *= Math.pow(2, zoom - opt.style.scaled);
return radius;
}
});
scaleOnZoom = true;
}
features = [{
point: {
x: opt.position.x,
y: opt.position.y,
style: opt.style
style: style,
scaleOnZoom: scaleOnZoom
}
}];
break;
Expand Down Expand Up @@ -786,7 +812,7 @@ var pointAnnotation = function (args) {
* @return {array} a list of style names to store.
*/
this._geojsonStyles = function () {
return ['fill', 'fillColor', 'fillOpacity', 'radius', 'stroke',
return ['fill', 'fillColor', 'fillOpacity', 'radius', 'scaled', 'stroke',
'strokeColor', 'strokeOpacity', 'strokeWidth'];
};

Expand Down
43 changes: 39 additions & 4 deletions src/annotationLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var annotationLayer = function (args) {
'fillColor': {dataType: 'color', keys: ['fillColor', 'fill-color', 'marker-color', 'fill']},
'fillOpacity': {dataType: 'opacity', keys: ['fillOpacity', 'fill-opacity']},
'radius': {dataType: 'positive', keys: ['radius']},
'scaled': {dataType: 'booleanOrNumber', keys: ['scaled']},
'stroke': {dataType: 'boolean', keys: ['stroke']},
'strokeColor': {dataType: 'color', keys: ['strokeColor', 'stroke-color', 'stroke']},
'strokeOpacity': {dataType: 'opacity', keys: ['strokeOpacity', 'stroke-opacity']},
Expand Down Expand Up @@ -546,8 +547,13 @@ var annotationLayer = function (args) {
* object with r, g, b properties in the range of [0-1].
* opacity: a floating point number in the range [0, 1].
* positive: a floating point number greater than zero.
* boolean: the string 'false' and falsy values are false, all else is
* true. null and undefined are still considered invalid values.
* boolean: a string whose lowercase value is 'false', 'off', or 'no', and
* falsy values are false, all else is true. null and undefined are
* still considered invalid values.
* booleanOrNumber: a string whose lowercase value is 'false', 'off', no',
* 'true', 'on', or 'yes', falsy values that aren't 0, and true are
* handled as booleans. Otherwise, a floating point number that isn't
* NaN or an infinity.
* @param {number|string|object|boolean} value: the value to validate.
* @param {string} dataType: the data type for validation.
* @returns {number|string|object|boolean|undefined} the sanitized value or
Expand All @@ -558,8 +564,18 @@ var annotationLayer = function (args) {
return;
}
switch (dataType) {
case 'booleanOrNumber':
if ((!value && value !== 0) || ['true', 'false', 'off', 'on', 'no', 'yes'].indexOf(('' + value).toLowerCase()) >= 0) {
value = !!value && ['false', 'no', 'off'].indexOf(('' + value).toLowerCase()) < 0;
} else {
value = +value;
if (isNaN(value) || !isFinite(value)) {
return;
}
}
break;
case 'boolean':
value = !!value && value !== 'false';
value = !!value && ['false', 'no', 'off'].indexOf(('' + value).toLowerCase()) < 0;
break;
case 'color':
value = util.convertColor(value);
Expand All @@ -575,7 +591,7 @@ var annotationLayer = function (args) {
break;
case 'positive':
value = +value;
if (isNaN(value) || value <= 0) {
if (isNaN(value) || !isFinite(value) || value <= 0) {
return;
}
break;
Expand All @@ -599,6 +615,7 @@ var annotationLayer = function (args) {
$.each(m_features, function (idx, featureLevel) {
$.each(featureLevel, function (type, feature) {
feature.data = [];
delete feature.feature.scaleOnZoom;
});
});
$.each(m_annotations, function (annotation_idx, annotation) {
Expand Down Expand Up @@ -663,6 +680,9 @@ var annotationLayer = function (args) {
}
/* Collect the data for each feature */
m_features[idx][type].data.push(featureSpec.data || featureSpec);
if (featureSpec.scaleOnZoom) {
m_features[idx][type].feature.scaleOnZoom = true;
}
});
});
});
Expand All @@ -677,6 +697,19 @@ var annotationLayer = function (args) {
s_update.call(m_this, request);
};

/**
* Check if any features are marked that they need to be updated when a zoom
* occurs. If so, mark that feature as modified.
*/
this._handleZoom = function () {
var i, features = m_this.features();
for (i = 0; i < features.length; i += 1) {
if (features[i].scaleOnZoom) {
features[i].modified();
}
}
};

///////////////////////////////////////////////////////////////////////////
/**
* Initialize
Expand All @@ -694,6 +727,8 @@ var annotationLayer = function (args) {
m_this.geoOn(geo_event.mouseclick, m_this._handleMouseClick);
m_this.geoOn(geo_event.mousemove, m_this._handleMouseMove);

m_this.geoOn(geo_event.zoom, m_this._handleZoom);

return m_this;
};

Expand Down
26 changes: 25 additions & 1 deletion tests/cases/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ describe('geo.annotation', function () {
});
it('_geojsonStyles', function () {
var ann = geo.annotation.pointAnnotation();
expect(ann._geojsonStyles().length).toBe(8);
expect(ann._geojsonStyles().length).toBe(9);
});
it('_geojsonCoordinates', function () {
var ann = geo.annotation.pointAnnotation();
Expand Down Expand Up @@ -423,6 +423,30 @@ describe('geo.annotation', function () {
expect(ann.options('position')).toEqual({x: 10, y: 20});
expect(ann.state()).toBe(geo.annotation.state.done);
});
it('scaled radius', function () {
var map = create_map();
var layer = map.createLayer('annotation', {
annotations: ['point']
});
var ann = geo.annotation.pointAnnotation({
position: point, layer: layer, style: {scaled: true}});
var features = ann.features();
expect(features.length).toBe(1);
expect(features[0].point.x).toEqual(point.x);
expect(features[0].point.style.radius()).toBe(10);
map.zoom(3);
features = ann.features();
expect(features[0].point.style.radius()).toBe(5);
ann.options().style.radius = function () {
return map.zoom() > 6.5 ? 4 : 10;
};
map.zoom(6);
features = ann.features();
expect(features[0].point.style.radius()).toBe(40);
map.zoom(7);
features = ann.features();
expect(features[0].point.style.radius()).toBe(32);
});
});

describe('annotation registry', function () {
Expand Down
22 changes: 20 additions & 2 deletions tests/cases/annotationLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,20 @@ describe('geo.annotationLayer', function () {
});
expect(layer.annotations()[0].options('vertices')[0]).not.toEqual(layer.annotations()[0].options('vertices')[1]);
});
it('_handleZoom', function () {
layer.mode(null);
layer.removeAllAnnotations();
layer.addAnnotation(point);
layer._update();
var mod = layer.features()[0].getMTime();
layer._handleZoom();
expect(layer.features()[0].getMTime()).toBe(mod);
layer.annotations()[0].options({style: {scaled: true}});
layer._update();
mod = layer.features()[0].getMTime();
layer._handleZoom();
expect(layer.features()[0].getMTime()).toBeGreaterThan(mod);
});
it('_processSelection', function () {
layer.removeAllAnnotations();
layer._processSelection({
Expand Down Expand Up @@ -467,14 +481,16 @@ describe('geo.annotationLayer', function () {
properties: {
radius: -5,
fillColor: 'no such color',
fillOpacity: -1
fillOpacity: -1,
scaled: 'not a number'
}
};
layer.geojson(badattr, true);
var attr = layer.geojson().features[0].properties;
expect(attr.radius).toBeGreaterThan(0);
expect(attr.fillOpacity).toBeGreaterThan(0);
expect(attr.fillColor).toBe('#00ff00');
expect(attr.scaled).toBe(false);
var goodattr = {
type: 'Feature',
geometry: {
Expand All @@ -484,14 +500,16 @@ describe('geo.annotationLayer', function () {
properties: {
radius: 3,
fillColor: 'indigo',
fillOpacity: 0.3
fillOpacity: 0.3,
scaled: 'On'
}
};
layer.geojson(goodattr, true);
attr = layer.geojson().features[0].properties;
expect(attr.radius).toBe(3);
expect(attr.fillOpacity).toBe(0.3);
expect(attr.fillColor).toBe('#4b0082');
expect(attr.scaled).toBe(4);
});
});
it('Test destroy layer.', function () {
Expand Down