Skip to content

Commit

Permalink
Add a strokeOffset option to markers.
Browse files Browse the repository at this point in the history
This is useful, for instance, when using the ellipse marker and having
the ellipse scale with the map.  Setting the strokeOffset to 0 gives
behavior similar to a polygon.

Setting the strokeOffset to 1 allows setting the radius and strokeWidth
in the same manner as the pointFeature.

The default strokeOffset of -1 allows the radius to be the actual
maximal size of the marker.
  • Loading branch information
manthey committed Oct 11, 2019
1 parent 9ee5457 commit a0d5d58
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 40 deletions.
68 changes: 50 additions & 18 deletions src/markerFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ var pointFeature = require('./pointFeature');
* @typedef {geo.feature.styleSpec} geo.markerFeature.styleSpec
* @extends geo.feature.styleSpec
* @property {number|function} [radius=5] Radius of each marker in pixels.
* This includes the stroke width and the fill.
* This includes the stroke width if `strokeOffset` is -1, excludes it if
* `strokeOffset` is 1, and includes half the stroke width if `strokeOffset`
* is 0.
* @property {geo.geoColor|function} [strokeColor] Color to stroke each marker.
* @property {number|function} [strokeOpacity=1] Opacity for each marker's
* stroke. Opacity is on a [0-1] scale. Set this or `strokeWidth` to zero
* to not have a stroke.
* @property {number|function} [strokeWidth=1.25] The weight of the marker's
* stroke in pixels. Set this or `strokeOpacity` to zero to not have a
* stroke.
* @property {number|function} [strokeOffset=-1] The position of the stroke
* compared to the radius. This can only be -1, 0, or 1 (the sign of the
* value is used).
* @property {geo.geoColor|function} [fillColor] Color to fill each marker.
* @property {number|function} [fillOpacity=1] Opacity for each marker.
* Opacity is on a [0-1] scale. Set to zero to have no fill.
Expand Down Expand Up @@ -92,6 +97,7 @@ var markerFeature = function (arg) {
var pts, position,
radius = m_this.style.get('radius'),
strokeWidth = m_this.style.get('strokeWidth'),
strokeOffset = m_this.style.get('strokeOffset'),
scaleWithZoom = m_this.style.get('scaleWithZoom');

position = m_this.position();
Expand All @@ -104,25 +110,29 @@ var markerFeature = function (arg) {
pts = m_this.data().map(function (d, i) {
var pt = position(d, i);

let r = radius(d, i), s = strokeWidth(d, i);
let r = radius(d, i),
s = strokeWidth(d, i),
so = Math.sign(strokeOffset(d, i));
let rwiths = r + s * (so + 1) / 2, // radius with stroke
rwos = r + s * (so - 1) / 2; // radius without stroke
switch (scaleWithZoom(d, i)) {
case markerFeature.scaleMode.stroke:
if (r - s > m_maxFixedRadius) {
m_maxFixedRadius = r - s;
if (rwos > m_maxFixedRadius) {
m_maxFixedRadius = rwos;
}
if (s > m_maxZoomStroke) {
m_maxZoomStroke = s;
}
break;
case markerFeature.scaleMode.fill:
case markerFeature.scaleMode.all:
if (r > m_maxZoomRadius) {
m_maxZoomRadius = r;
if (rwiths > m_maxZoomRadius) {
m_maxZoomRadius = rwiths;
}
break;
default:
if (r > m_maxFixedRadius) {
m_maxFixedRadius = r;
if (rwiths > m_maxFixedRadius) {
m_maxFixedRadius = rwiths;
}
break;
}
Expand All @@ -133,6 +143,19 @@ var markerFeature = function (arg) {
m_rangeTreeTime.modified();
};

/**
* Determine an approximate maximum radius based on the zoom factor.
*
* @param {number} zoom The zoom level.
* @returns {number} The maximum radius. May be somewhat larger than the
* actual maximum.
*/
this._approximateMaxRadius = function (zoom) {
m_this._updateRangeTree();
let zoomFactor = Math.pow(2, zoom);
return Math.max(m_maxFixedRadius + m_maxZoomStroke * zoomFactor, m_maxZoomRadius * zoomFactor);
};

/**
* Returns an array of datum indices that contain the given marker.
*
Expand All @@ -146,6 +169,7 @@ var markerFeature = function (arg) {
corners,
radius = m_this.style.get('radius'),
strokeWidth = m_this.style.get('strokeWidth'),
strokeOffset = m_this.style.get('strokeOffset'),
scaleWithZoom = m_this.style.get('scaleWithZoom');

data = m_this.data();
Expand All @@ -163,8 +187,8 @@ var markerFeature = function (arg) {
var map = m_this.layer().map(),
pt = map.gcsToDisplay(p),
zoom = map.zoom(),
zfactor = Math.pow(2, zoom),
maxr = Math.max(m_maxFixedRadius + m_maxZoomStroke * zfactor, m_maxZoomRadius * zfactor);
zoomFactor = Math.pow(2, zoom),
maxr = this._approximateMaxRadius(zoom);

// check all corners to make sure we handle rotations
corners = [
Expand All @@ -191,18 +215,21 @@ var markerFeature = function (arg) {
var d = data[i],
rad = radius(data[i], i),
swz = scaleWithZoom(data[i], i),
so = strokeOffset(data[i], i),
s = swz ? strokeWidth(data[i], i) : 0;
var rwos = rad + s * (so - 1) / 2; // radius without stroke
rad = rwos + s;
var p = m_this.position()(d, i),
dx, dy, rad2;
switch (swz) {
case markerFeature.scaleMode.fill:
rad = (rad - s) * zfactor + s;
rad = rwos * zoomFactor + s;
break;
case markerFeature.scaleMode.stroke:
rad = (rad - s) + s * zfactor;
rad = rwos + s * zoomFactor;
break;
case markerFeature.scaleMode.all:
rad *= zfactor;
rad *= zoomFactor;
break;
}

Expand Down Expand Up @@ -252,11 +279,12 @@ var markerFeature = function (arg) {
data = m_this.data(),
radius = m_this.style.get('radius'),
strokeWidth = m_this.style.get('strokeWidth'),
strokeOffset = m_this.style.get('strokeOffset'),
scaleWithZoom = m_this.style.get('scaleWithZoom'),
idx, min, max, corners,
zoom = map.zoom(),
zfactor = Math.pow(2, zoom),
maxr = Math.max(m_maxFixedRadius + m_maxZoomStroke * zfactor, m_maxZoomRadius * zfactor);
zoomFactor = Math.pow(2, zoom),
maxr = this._approximateMaxRadius(zoom);

if (!poly.outer) {
poly = {outer: poly, inner: []};
Expand Down Expand Up @@ -308,16 +336,19 @@ var markerFeature = function (arg) {
let p = m_this.position()(d, i);
let rad = radius(data[i], i),
swz = scaleWithZoom(data[i], i),
so = strokeOffset(data[i], i),
s = swz ? strokeWidth(data[i], i) : 0;
let rwos = rad + s * (so - 1) / 2; // radius without stroke
rad = rwos + s;
switch (swz) {
case markerFeature.scaleMode.fill:
rad = (rad - s) * zfactor + s;
rad = rwos * zoomFactor + s;
break;
case markerFeature.scaleMode.stroke:
rad = (rad - s) + s * zfactor;
rad = rwos + s * zoomFactor;
break;
case markerFeature.scaleMode.all:
rad *= zfactor;
rad *= zoomFactor;
break;
}
if (rad) {
Expand Down Expand Up @@ -353,6 +384,7 @@ var markerFeature = function (arg) {
{
radius: 6.25,
strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
strokeOffset: -1.0,
strokeOpacity: 1.0,
strokeWidth: 1.25,
fillColor: { r: 1.0, g: 0.839, b: 0.439 },
Expand Down
62 changes: 44 additions & 18 deletions src/webgl/markerFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var webgl_markerFeature = function (arg) {
var util = require('../util');
var object = require('./object');
var pointUtil = require('./pointUtil.js');
var geo_event = require('../event');
var fragmentShaderPoly = require('./markerFeaturePoly.frag');
var fragmentShaderSprite = require('./markerFeatureSprite.frag');
var vertexShaderPoly = require('./markerFeaturePoly.vert');
Expand Down Expand Up @@ -104,14 +105,14 @@ var webgl_markerFeature = function (arg) {
unit = m_this._pointPolygon(0, 0, 1, 1),
position = new Array(numPts * 3), posBuf, posVal, posFunc,
indices, unitBuf,
zoomFactor = Math.pow(2, m_this.renderer().map().zoom()),
styleBuf = {}, styleVal = {}, styleFunc = {}, styleUni = {},
styleKeys = {
radius: 1,
fillColor: 3,
fillOpacity: 1,
strokeColor: 3,
strokeOpacity: 1,
strokeOffset: 0,
strokeWidth: 1,
symbol: 1,
symbolValue: 1,
Expand All @@ -121,7 +122,7 @@ var webgl_markerFeature = function (arg) {
},
vpf = m_this.verticesPerFeature(),
data = m_this.data(),
item, ivpf, ivpf3, iunit, i3, maxr = 0,
item, ivpf, ivpf3, iunit, i3,
geom = m_mapper.geometryData();

posFunc = m_this.position();
Expand Down Expand Up @@ -188,14 +189,11 @@ var webgl_markerFeature = function (arg) {
styleVal.scaleWithZoom +
(styleVal.rotateWithMap ? 4 : 0) +
// bit 3 reserved
styleVal.symbol * 16);
((Math.sign(styleVal.strokeOffset) + 1) * 16) +
styleVal.symbol * 64);
if (styleVal.symbolValue && styleVal.symbol >= markerFeature.symbols.arrow && styleVal.symbol < markerFeature.symbols.arrow + markerFeature.symbols.arrowMax) {
styleVal.symbolValue = packFloats(styleVal.symbolValue);
}
if (m_this._primitiveShapeAuto &&
(styleVal.scaleWithZoom ? styleVal.radius * zoomFactor : styleVal.radius) > maxr) {
maxr = styleVal.scaleWithZoom ? styleVal.radius * zoomFactor : styleVal.radius;
}
for (j = 0; j < vpf; j += 1, ivpf += 1, ivpf3 += 3) {
if (!onlyStyle) {
posBuf[ivpf3] = position[i3];
Expand All @@ -218,16 +216,18 @@ var webgl_markerFeature = function (arg) {
}
}

if (m_this._primitiveShapeAuto &&
((m_this._primitiveShape === markerFeature.primitiveShapes.sprite && maxr > webglRenderer._maxPointSize) ||
(m_this._primitiveShape !== markerFeature.primitiveShapes.sprite && maxr <= webglRenderer._maxPointSize))) {
// Switch primitive
m_this._primitiveShape = maxr > webglRenderer._maxPointSize ? markerFeature.primitiveShapes.triangle : markerFeature.primitiveShapes.sprite;
m_this.renderer().contextRenderer().removeActor(m_actor);
m_actor = null;
m_this._init(true);
createGLMarkers();
return;
if (m_this._primitiveShapeAuto) {
let maxr = m_this._approximateMaxRadius(m_this.renderer().map().zoom());
if ((m_this._primitiveShape === markerFeature.primitiveShapes.sprite && maxr > webglRenderer._maxPointSize) ||
(m_this._primitiveShape !== markerFeature.primitiveShapes.sprite && maxr <= webglRenderer._maxPointSize)) {
// Switch primitive
m_this._primitiveShape = maxr > webglRenderer._maxPointSize ? markerFeature.primitiveShapes.triangle : markerFeature.primitiveShapes.sprite;
m_this.renderer().contextRenderer().removeActor(m_actor);
m_actor = null;
m_this._init(true);
createGLMarkers();
return;
}
}

if (!onlyStyle) {
Expand All @@ -251,6 +251,24 @@ var webgl_markerFeature = function (arg) {
return [m_actor];
};

/**
* Handle zoom events for automatic primive shape adjustment.
*
* @param {number} zoom The new zoom level.
*/
this._handleZoom = function (zoom) {
if (!m_this._primitiveShapeAuto || m_this._primitiveShape !== markerFeature.primitiveShapes.sprite) {
return;
}
if (m_this._approximateMaxRadius(zoom) > webglRenderer._maxPointSize) {
m_this._primitiveShape = markerFeature.primitiveShapes.triangle;
m_this.renderer().contextRenderer().removeActor(m_this.actors()[0]);
m_this._init(true);
m_this.dataTime().modified();
m_this.draw();
}
};

/**
* Initialize.
*
Expand All @@ -263,7 +281,7 @@ var webgl_markerFeature = function (arg) {
mat = vgl.material(),
blend = vgl.blend(),
geom = vgl.geometryData(),
sourcePositions = vgl.sourceDataP3fv({'name': 'pos'}),
sourcePositions = vgl.sourceDataP3fv({name: 'pos'}),
attr = {
radius: 1,
fillColor: 3,
Expand Down Expand Up @@ -301,10 +319,12 @@ var webgl_markerFeature = function (arg) {
prog.addVertexAttribute(vgl.vertexAttribute(key), idx + 1);
geom.addSource(vgl.sourceDataAnyfv(attr[key], idx + 1, {name: key}));
});
m_uniforms = {};
Object.keys(uniforms).forEach((key) => {
m_uniforms[key] = new vgl.uniform(uniforms[key], key);
prog.addUniform(m_uniforms[key]);
});

prog.addUniform(m_modelViewUniform);
prog.addUniform(projectionUniform);

Expand All @@ -325,6 +345,11 @@ var webgl_markerFeature = function (arg) {
geom.setBounds(0, 0, 0, 0, 0, 0);
};
m_mapper.setGeometryData(geom);
if (!reinit) {
m_this.geoOn(geo_event.zoom, function (evt) {
m_this._handleZoom(evt.zoomLevel);
});
}
};

/**
Expand Down Expand Up @@ -376,6 +401,7 @@ var webgl_markerFeature = function (arg) {
this._exit = function () {
m_this.renderer().contextRenderer().removeActor(m_actor);
m_actor = null;
m_uniforms = {};
s_exit();
};

Expand Down
2 changes: 1 addition & 1 deletion src/webgl/markerFeatureFS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ void markerFeatureFragment(vec2 pos) {
pos = vec2(pos.x * cosr + pos.y * sinr, -pos.x * sinr + pos.y * cosr);
}

int symbol = int(floor(symbolVar / 16.0));
int symbol = int(floor(symbolVar / 64.0));
bool isimage = bool(mod(floor(symbolVar / 8.0), 2.0));
vec4 fillColor, strokeColor;
float endStep;
Expand Down
2 changes: 2 additions & 0 deletions src/webgl/markerFeatureVS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ float markerFeaturePrep(void)
radiusVar = radius;
strokeWidthVar = strokeWidth;
int scaleMode = int(mod(symbol, 4.0));
float strokeOffset = mod(floor(symbol / 16.0), 4.0) - 1.0;
radiusVar += (strokeOffset + 1.0) / 2.0 * strokeWidthVar;
if (scaleMode == 1) { // fill
radiusVar = (radiusVar - strokeWidthVar) * exp2(zoom) + strokeWidthVar;
} else if (scaleMode == 2) { // stroke
Expand Down
2 changes: 1 addition & 1 deletion src/webgl/pointUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function pointUtil(m_this, arg) {
if (update) {
m_this.renderer().contextRenderer().removeActor(m_this.actors()[0]);
m_this._init(true);
m_this.modified();
m_this.dataTime().modified();
}
}
return m_this;
Expand Down
Loading

0 comments on commit a0d5d58

Please sign in to comment.