diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c6504f9b..8dafc64a79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ ### Improvements - More response zooming via mouse wheel (#993) - Explicitly exit retired renderers when autosharing renderers (#1007) +- If a point has no stroke or fill, don't return it from pointSearch (#1003) +- WebGL point, line, polygon, and contour features use a localized origin for improved precision at high zoom levels. This reduces panning jitter in zoom levels 19 and up (#1005) +- When doing a point search on a line feature, report which line segment is found (#1008) ### Changes - Idle handlers no longer defer to scene-graph parents. Parents still wait for all children to be idle (#1001) diff --git a/src/contourFeature.js b/src/contourFeature.js index f72989c2f9..9ccefc2432 100644 --- a/src/contourFeature.js +++ b/src/contourFeature.js @@ -28,6 +28,11 @@ var meshFeature = require('./meshFeature'); * that point won't be included in the results. * @property {number|function} [opacity=1] The opacity for the whole feature on * a scale of 0 to 1. + * @property {number[]|function} [origin] Origin in map gcs coordinates used + * for to ensure high precision drawing in this location. When called as a + * function, this is passed the vertex positions as a single continuous array + * in map gcs coordinates. It defaults to the first vertex used in the + * contour. */ /** @@ -214,7 +219,8 @@ var contourFeature = function (arg) { opacity: 1.0, value: function (d, i) { return m_this.position()(d, i).z; - } + }, + origin: (p) => (p.length >= 3 ? [p[0], p[1], 0] : [0, 0, 0]) }, arg.style === undefined ? {} : arg.style ); diff --git a/src/lineFeature.js b/src/lineFeature.js index a3012b0d4d..079251214a 100644 --- a/src/lineFeature.js +++ b/src/lineFeature.js @@ -50,6 +50,11 @@ var util = require('./util'); * visible gradient. This is a single value that applies to all lines. * @property {string|function} [debug] If 'debug', render lines in debug mode. * This is a single value that applies to all lines. + * @property {number[]|function} [origin] Origin in map gcs coordinates used + * for to ensure high precision drawing in this location. When called as a + * function, this is passed the vertex positions as a single continuous array + * in map gcs coordinates. It defaults to the first line's first vertex's + * position. */ /** @@ -353,7 +358,8 @@ var lineFeature = function (arg) { antialiasing: 2.0, closed: false, line: function (d) { return d; }, - position: function (d) { return d; } + position: function (d) { return d; }, + origin: (p) => (p.length >= 3 ? p.slice(0, 3) : [0, 0, 0]) }, arg.style === undefined ? {} : arg.style ); diff --git a/src/pointFeature.js b/src/pointFeature.js index ffe577eaa8..e602d98e2a 100644 --- a/src/pointFeature.js +++ b/src/pointFeature.js @@ -37,6 +37,10 @@ var feature = require('./feature'); * @property {geo.geoColor|function} [fillColor] Color to fill each point. * @property {number|function} [fillOpacity=1] Opacity for each point. Opacity * is on a [0-1] scale. + * @property {number[]|function} [origin] Origin in map gcs coordinates used + * for to ensure high precision drawing in this location. When called as a + * function, this is passed the point positions as a single continuous array + * in map gcs coordinates. It defaults to the first point's position. */ /** @@ -312,6 +316,7 @@ var pointFeature = function (arg) { // Find points inside the bounding box idx = m_rangeTree.range(min.x, min.y, max.x, max.y); + idx.sort((a, b) => a - b); // Filter by circular region idx.forEach(function (i) { var d = data[i], @@ -411,7 +416,8 @@ var pointFeature = function (arg) { fillColor: { r: 1.0, g: 0.839, b: 0.439 }, fill: true, fillOpacity: 0.8, - position: function (d) { return d; } + position: function (d) { return d; }, + origin: (p) => (p.length >= 3 ? p.slice(0, 3) : [0, 0, 0]) }, arg.style === undefined ? {} : arg.style ); diff --git a/src/polygonFeature.js b/src/polygonFeature.js index db0cf07eff..33bc9f1ad9 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -30,6 +30,11 @@ var transform = require('./transform'); * each polygon has a uniform style (uniform fill color, fill opacity, stroke * color, and stroke opacity). Can vary by polygon. * @property {boolean|function} [closed=true] Ignored. Always `true`. + * @property {number[]|function} [origin] Origin in map gcs coordinates used + * for to ensure high precision drawing in this location. When called as a + * function, this is passed an array of items, each of which has a vertices + * property that is a single continuous array in map gcs coordinates. It + * defaults to the first polygon's first vertex's position. */ /** @@ -583,7 +588,15 @@ var polygonFeature = function (arg) { strokeColor: {r: 0.0, g: 1.0, b: 1.0}, strokeOpacity: 1.0, polygon: function (d) { return d; }, - position: function (d) { return d; } + position: function (d) { return d; }, + origin: (items) => { + for (let i = 0; i < items.length; i += 1) { + if (items[i].vertices.length >= 3) { + return items[i].vertices.slice(0, 3); + } + } + return [0, 0, 0]; + } }, arg.style === undefined ? {} : arg.style ); diff --git a/src/quadFeature.js b/src/quadFeature.js index 774c306b7b..dc6cd029a3 100644 --- a/src/quadFeature.js +++ b/src/quadFeature.js @@ -583,7 +583,7 @@ var quadFeature = function (arg) { clrQuads: clrQuads, imgQuads: imgQuads, vidQuads: vidQuads, - origin: origin + origin: new Float32Array(origin) }; return m_quads; }; diff --git a/src/webgl/contourFeature.js b/src/webgl/contourFeature.js index d058ebd659..a5a0d55618 100644 --- a/src/webgl/contourFeature.js +++ b/src/webgl/contourFeature.js @@ -44,6 +44,8 @@ var webgl_contourFeature = function (arg) { m_stepsUniform = null, m_steppedUniform = null, m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, + m_origin, + m_modelViewUniform, s_init = this._init, s_update = this._update; @@ -95,6 +97,16 @@ var webgl_contourFeature = function (arg) { m_texture.setColorTable(colorTable); contour.pos = transform.transformCoordinates( m_this.gcs(), m_this.layer().map().gcs(), contour.pos, 3); + m_origin = new Float32Array(m_this.style.get('origin')(contour.pos)); + if (m_origin[0] || m_origin[1] || m_origin[2]) { + for (i = 0; i < contour.pos.length; i += 3) { + contour.pos[i] -= m_origin[0]; + contour.pos[i + 1] -= m_origin[1]; + contour.pos[i + 2] -= m_origin[2]; + } + } + m_modelViewUniform.setOrigin(m_origin); + posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3); opacityBuf = util.getGeomBuffer(geom, 'opacity', numPts); valueBuf = util.getGeomBuffer(geom, 'value', numPts); @@ -128,7 +140,6 @@ var webgl_contourFeature = function (arg) { mat = vgl.material(), tex = vgl.lookupTable(), geom = vgl.geometryData(), - modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'), projectionUniform = new vgl.projectionUniform('projectionMatrix'), samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'), vertexShader = createVertexShader(), @@ -142,6 +153,7 @@ var webgl_contourFeature = function (arg) { sourceOpacity = vgl.sourceDataAnyfv( 1, vgl.vertexAttributeKeysIndexed.Two, {'name': 'opacity'}), primitive = new vgl.triangles(); + m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix'); s_init.call(m_this, arg); m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw}); @@ -150,7 +162,7 @@ var webgl_contourFeature = function (arg) { prog.addVertexAttribute(valueAttr, vgl.vertexAttributeKeysIndexed.One); prog.addVertexAttribute(opacityAttr, vgl.vertexAttributeKeysIndexed.Two); - prog.addUniform(modelViewUniform); + prog.addUniform(m_modelViewUniform); prog.addUniform(projectionUniform); m_minColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'minColor'); prog.addUniform(m_minColorUniform); diff --git a/src/webgl/lineFeature.js b/src/webgl/lineFeature.js index 241fe2271b..40d7003732 100644 --- a/src/webgl/lineFeature.js +++ b/src/webgl/lineFeature.js @@ -75,6 +75,8 @@ var webgl_lineFeature = function (arg) { m_flagsUniform, m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw, m_geometry, + m_origin, + m_modelViewUniform, s_init = this._init, s_update = this._update; @@ -186,6 +188,16 @@ var webgl_lineFeature = function (arg) { position = transform.transformCoordinates( m_this.gcs(), m_this.layer().map().gcs(), position, 3); + m_origin = new Float32Array(m_this.style.get('origin')(position)); + if (m_origin[0] || m_origin[1] || m_origin[2]) { + for (i = 0; i < position.length; i += 3) { + position[i] -= m_origin[0]; + position[i + 1] -= m_origin[1]; + position[i + 2] -= m_origin[2]; + } + } + m_modelViewUniform.setOrigin(m_origin); + len = numSegments * orderLen; posBuf = util.getGeomBuffer(geom, 'pos', len * 3); prevBuf = util.getGeomBuffer(geom, 'prev', len * 3); @@ -381,7 +393,6 @@ var webgl_lineFeature = function (arg) { strkColorAttr = vgl.vertexAttribute('strokeColor'), strkOpacityAttr = vgl.vertexAttribute('strokeOpacity'), // Shader uniforms - mviUnif = new vgl.modelViewUniform('modelViewMatrix'), prjUnif = new vgl.projectionUniform('projectionMatrix'), geom = vgl.geometryData(), // Sources @@ -402,6 +413,7 @@ var webgl_lineFeature = function (arg) { 1, vgl.vertexAttributeKeysIndexed.Three, {name: 'strokeOpacity'}), // Primitive indices triangles = vgl.triangles(); + m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix'); m_pixelWidthUnif = new vgl.floatUniform( 'pixelWidth', 1.0 / m_this.renderer().width()); @@ -424,7 +436,7 @@ var webgl_lineFeature = function (arg) { prog.addVertexAttribute(farAttr, vgl.vertexAttributeKeysIndexed.Six); prog.addVertexAttribute(flagsAttr, vgl.vertexAttributeKeysIndexed.Seven); - prog.addUniform(mviUnif); + prog.addUniform(m_modelViewUniform); prog.addUniform(prjUnif); prog.addUniform(m_pixelWidthUnif); prog.addUniform(m_aspectUniform); diff --git a/src/webgl/pointFeature.js b/src/webgl/pointFeature.js index 07de3cb3d4..0933f6e167 100644 --- a/src/webgl/pointFeature.js +++ b/src/webgl/pointFeature.js @@ -47,6 +47,8 @@ var webgl_pointFeature = function (arg) { * 'triangle' seems to be fastest on low-powered hardware, but 'square' * visits fewer fragments. */ m_primitiveShape = 'sprite', // arg can change this, below + m_modelViewUniform, + m_origin, s_init = this._init, s_update = this._update, s_updateStyleFromArray = this.updateStyleFromArray; @@ -167,6 +169,15 @@ var webgl_pointFeature = function (arg) { } position = transform.transformCoordinates( m_this.gcs(), m_this.layer().map().gcs(), position, 3); + m_origin = new Float32Array(m_this.style.get('origin')(position)); + if (m_origin[0] || m_origin[1] || m_origin[2]) { + for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { + position[i3] -= m_origin[0]; + position[i3 + 1] -= m_origin[1]; + position[i3 + 2] -= m_origin[2]; + } + } + m_modelViewUniform.setOrigin(m_origin); posBuf = util.getGeomBuffer(geom, 'pos', vpf * numPts * 3); @@ -363,7 +374,6 @@ var webgl_pointFeature = function (arg) { strokeAttr = vgl.vertexAttribute('stroke'), fillOpacityAttr = vgl.vertexAttribute('fillOpacity'), strokeOpacityAttr = vgl.vertexAttribute('strokeOpacity'), - modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'), projectionUniform = new vgl.projectionUniform('projectionMatrix'), mat = vgl.material(), blend = vgl.blend(), @@ -388,6 +398,7 @@ var webgl_pointFeature = function (arg) { sourceStrokeOpacity = vgl.sourceDataAnyfv( 1, vgl.vertexAttributeKeysIndexed.Nine, {'name': 'strokeOpacity'}), primitive = new vgl.triangles(); + m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix', m_origin); if (m_primitiveShape === 'sprite') { primitive = new vgl.points(); @@ -401,7 +412,6 @@ var webgl_pointFeature = function (arg) { s_init.call(m_this, arg); m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw}); - // TODO: Right now this is ugly but we will fix it. prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); if (m_primitiveShape !== 'sprite') { prog.addVertexAttribute(unitAttr, vgl.vertexAttributeKeysIndexed.One); @@ -418,7 +428,7 @@ var webgl_pointFeature = function (arg) { prog.addUniform(m_pixelWidthUniform); prog.addUniform(m_aspectUniform); - prog.addUniform(modelViewUniform); + prog.addUniform(m_modelViewUniform); prog.addUniform(projectionUniform); prog.addShader(fragmentShader); diff --git a/src/webgl/polygonFeature.js b/src/webgl/polygonFeature.js index 220cf358b8..adfdcdd6a8 100644 --- a/src/webgl/polygonFeature.js +++ b/src/webgl/polygonFeature.js @@ -38,6 +38,8 @@ var webgl_polygonFeature = function (arg) { m_mapper = vgl.mapper(), m_material = vgl.material(), m_geometry, + m_origin, + m_modelViewUniform, s_init = this._init, s_update = this._update, m_builtOnce, @@ -167,6 +169,8 @@ var webgl_polygonFeature = function (arg) { geom.primitive(0).setIndices(indices); } m_geometry = {items: items, numPts: numPts}; + m_origin = new Float32Array(m_this.style.get('origin')(items)); + m_modelViewUniform.setOrigin(m_origin); } else { items = m_geometry.items; numPts = m_geometry.numPts; @@ -214,9 +218,9 @@ var webgl_polygonFeature = function (arg) { } else { j = items[k].triangles[i] * 3; if (!onlyStyle) { - posBuf[d3] = vertices[j]; - posBuf[d3 + 1] = vertices[j + 1]; - posBuf[d3 + 2] = vertices[j + 2]; + posBuf[d3] = vertices[j] - m_origin[0]; + posBuf[d3 + 1] = vertices[j + 1] - m_origin[1]; + posBuf[d3 + 2] = vertices[j + 2] - m_origin[2]; indices[d] = i; } if (!uniform && fillColorVal === undefined) { @@ -255,7 +259,6 @@ var webgl_polygonFeature = function (arg) { posAttr = vgl.vertexAttribute('pos'), fillColorAttr = vgl.vertexAttribute('fillColor'), fillOpacityAttr = vgl.vertexAttribute('fillOpacity'), - modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'), projectionUniform = new vgl.projectionUniform('projectionMatrix'), vertexShader = createVertexShader(), fragmentShader = createFragmentShader(), @@ -267,12 +270,13 @@ var webgl_polygonFeature = function (arg) { sourceFillOpacity = vgl.sourceDataAnyfv( 1, vgl.vertexAttributeKeysIndexed.Three, {'name': 'fillOpacity'}), trianglePrimitive = vgl.triangles(); + m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix'); prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Two); prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Three); - prog.addUniform(modelViewUniform); + prog.addUniform(m_modelViewUniform); prog.addUniform(projectionUniform); prog.addShader(fragmentShader); diff --git a/tests/external-data/base-images.tgz.sha512 b/tests/external-data/base-images.tgz.sha512 index 5d43a1957e..8600d9df70 100644 --- a/tests/external-data/base-images.tgz.sha512 +++ b/tests/external-data/base-images.tgz.sha512 @@ -1 +1 @@ -3b3eb2d6d4bf78cd9ff98319a7745ca7a3c115ec8d47a706165e5bdb224e8e49ba122acacb647ae4edb994fe3e3ab7e1456bfa1577c7dd0fe6e1b524f37d186d \ No newline at end of file +28a26c252b7e013ad542fb3be0286f22b153b6bb8a791e3aef118c54944094807f041e1b4073a02c7a14f45848740dd63e5c6f20141e8801db8a0594ffb8efc5 \ No newline at end of file