From 4e972f7f9502a1ec313f58847eb921f2d6cf8b5e Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 30 Aug 2016 09:06:36 -0400 Subject: [PATCH] Refactored polygons so that position and polygon are style properties. Before they were special properties, which made them harder to work with. Added mouseDown information to mouse click events, since the click is triggered with mouse up, buttons are typically up. Crude, if functional, support for polygon and rectangle annotations. --- src/annotationLayer.js | 180 +++++++++++++++++++++++++--------- src/feature.js | 2 +- src/gl/lineFeature.js | 6 ++ src/gl/polygonFeature.js | 16 ++- src/mapInteractor.js | 14 ++- src/polygonFeature.js | 59 +++++------ tests/cases/polygonFeature.js | 8 +- 7 files changed, 196 insertions(+), 89 deletions(-) diff --git a/src/annotationLayer.js b/src/annotationLayer.js index 92b5ca2c02..29ea428f5d 100644 --- a/src/annotationLayer.js +++ b/src/annotationLayer.js @@ -34,10 +34,12 @@ var annotationLayer = function (args) { m_buildTime = timestamp(), m_options, m_actions, + m_mode, m_annotations = [], m_features = []; m_options = $.extend(true, {}, { + dblClickTime: 300, actions: [{ action: 'annotationmenu', // this will redirect to the appropriate menu input: 'right' @@ -81,9 +83,8 @@ var annotationLayer = function (args) { }, args); this._processSelection = function (event) { - if (m_this.mode === 'rectangle') { - m_this.map().node().css('cursor', ''); - m_this.map().interactor().removeAction(m_actions.rectangle); + if (m_this.mode() === 'rectangle') { + m_this.mode(null); if (event.state.action === geo_action.annotation_rectangle) { var map = m_this.map(); var params = { @@ -101,7 +102,10 @@ var annotationLayer = function (args) { }; this._handleMouseMove = function (evt) { - switch (m_this.mode && m_this.currentAnnotation) { + if (!m_this.mode() || !m_this.currentAnnotation) { + return; + } + switch (m_this.mode()) { case 'polygon': var vertices = m_this.currentAnnotation.options().vertices; if (vertices.length) { @@ -111,42 +115,70 @@ var annotationLayer = function (args) { m_this._update(); m_this.draw(); break; - default: - return; } }; this._handleMouseClick = function (evt) { - console.log('event', evt, m_this.currentAnnotation); //DWM:: - - if (m_this.mode !== 'polygon') { - console.log('--------START----------'); //DWM:: - m_this.mode = 'polygon'; - m_this.map().node().css('cursor', 'crosshair'); - m_this.currentAnnotation = polygonAnnotation({state: 'create'}); - this.addAnnotation(m_this.currentAnnotation); - return; + switch (m_this.mode()) { + case 'polygon': + var end = !!evt.buttonsDown.right, skip; + if (!evt.buttonsDown.left && !evt.buttonsDown.right) { + break; + } + var vertices = m_this.currentAnnotation.options().vertices; + if (evt.buttonsDown.left) { + if (vertices.length) { + if (vertices.length >= 2 && + vertices[vertices.length - 2].x === evt.mapgcs.x && + vertices[vertices.length - 2].y === evt.mapgcs.y) { + skip = true; + if (m_this.currentAnnotation.lastClick && + evt.time - m_this.currentAnnotation.lastClick < m_options.dblClickTime) { + end = true; + } + } else if (vertices.length >= 2 && vertices[0].x === evt.mapgcs.x && + vertices[0].y === evt.mapgcs.y) { + end = true; + } else { + vertices[vertices.length - 1] = evt.mapgcs; + } + } else { + vertices.push(evt.mapgcs); + } + if (!end && !skip) { + vertices.push(evt.mapgcs); + } + m_this.currentAnnotation.lastClick = evt.time; + } else { + if (vertices.length > 1) { + vertices.pop(); + } + } + if (end) { + if (vertices.length < 3) { + m_this.removeAnnotation(m_this.currentAnnotation, false); + } else { + m_this.currentAnnotation.state('done'); + } + m_this.mode(null); + } + m_this.modified(); + m_this._update(); + m_this.draw(); + evt.handled = true; + return; } - if (m_this.mode === 'polygon') { - // if right click or click near first point, end the polygon - var vertices = m_this.currentAnnotation.options().vertices; - if (vertices.length) { - vertices[vertices.length - 1] = evt.mapgcs; - } - vertices.push(evt.mapgcs); - m_this.modified(); - m_this._update(); - m_this.draw(); + }; + this._mouseClickToStart = function (evt) { + if (evt.handled) { return; } - - if (!m_this.map().interactor().removeAction(m_actions.rectangle)) { - m_this.mode = 'rectangle'; - m_this.map().node().css('cursor', 'crosshair'); - m_this.map().interactor().addAction(m_actions.rectangle); + if (!m_this.mode()) { + m_this.mode(evt.buttonsDown.left ? 'polygon' : 'rectangle'); + } else { + m_this.mode(null); } - m_this.mode = null; }; this.addAnnotation = function (annotation) { @@ -156,6 +188,42 @@ var annotationLayer = function (args) { this.draw(); }; + this.removeAnnotation = function (annotation, update) { + var pos = $.inArray(annotation, m_annotations); + if (pos >= 0) { + m_annotations.splice(pos, 1); + if (update !== false) { + this.modified(); + this._update(); + this.draw(); + } + } + return pos >= 0; + }; + + this.mode = function (arg) { + if (arg === undefined) { + return m_mode; + } + if (arg !== m_mode) { + m_mode = arg; + m_this.map().node().css('cursor', m_mode ? 'crosshair' : ''); + this.currentAnnotation = null; + switch (m_mode) { + case 'polygon': + this.currentAnnotation = polygonAnnotation({state: 'create'}); + this.addAnnotation(m_this.currentAnnotation); + break; + case 'rectangle': + m_this.map().interactor().addAction(m_actions.rectangle); + break; + } + if (m_mode !== 'rectangle') { + m_this.map().interactor().removeAction(m_actions.rectangle); + } + } + }; + /////////////////////////////////////////////////////////////////////////// /** * Update layer @@ -176,16 +244,25 @@ var annotationLayer = function (args) { } $.each(featureLevel, function (type, featureSpec) { if (!m_features[idx][type]) { - var feature = m_this.createFeature(type, { - gcs: m_this.map().gcs(), - polygon: function (d) { - return d.polygon; + try { + var feature = m_this.createFeature(type, { + gcs: m_this.map().gcs() + }); + } catch (err) { + /* We can't create the desired feature, porbably because of the + * selected renderer. Issue one warning only. */ + var key = 'error_feature_' + type; + if (!m_this[key]) { + console.warning('Cannot create a ' + type + ' feature for ' + + 'annotations.'); + m_this[key] = true; } - }); + return; + } var style = {}; - $.each(['fill', 'fillColor', 'fillOpacity', 'stroke', - 'strokeColor', 'strokeOpacity', 'strokeWidth', - 'uniformPolygon' + $.each(['fill', 'fillColor', 'fillOpacity', 'line', 'polygon', + 'position', 'stroke', 'strokeColor', 'strokeOpacity', + 'strokeWidth', 'uniformPolygon' ], function (keyidx, key) { var origFunc; if (feature.style()[key] !== undefined) { @@ -195,7 +272,7 @@ var annotationLayer = function (args) { var style = ( (d && d.style) ? d.style : (d && d[2] && d[2].style) ? d[2].style : d2.style); - var result = style[key]; + var result = style ? style[key] : d; if (util.isFunction(result)) { result = result(d, i, d2, i2); } @@ -212,7 +289,7 @@ var annotationLayer = function (args) { data: [] }; } - m_features[idx][type].data.push(featureSpec); + m_features[idx][type].data.push(featureSpec.data || featureSpec); }); }); }); @@ -240,10 +317,10 @@ var annotationLayer = function (args) { } m_this.geoOn(geo_event.actionselection, m_this._processSelection); - console.log('get mouse click'); //DWM:: m_this.geoOn(geo_event.mouseclick, m_this._handleMouseClick); m_this.geoOn(geo_event.mousemove, m_this._handleMouseMove); + m_this.geoOn(geo_event.mouseclick, m_this._mouseClickToStart); //DWM:: capture mouse actions on the layer and on child features. console.log(m_options); //DWM:: return m_this; @@ -335,7 +412,7 @@ var annotation = function (type, args) { this.geojson = function () { // return the annotation as a geojson object - //DWM: + //DWM:: }; }; @@ -360,6 +437,7 @@ var rectangleAnnotation = function (args) { fill: true, fillColor: {r: 0, g: 1, b: 0}, fillOpacity: 0.25, + polygon: function (d) { return d.polygon; }, stroke: true, strokeColor: {r: 0, g: 0, b: 0}, strokeOpacity: 1, @@ -391,6 +469,9 @@ var polygonAnnotation = function (args) { if (!(this instanceof polygonAnnotation)) { return new polygonAnnotation(args); } + + var m_this = this; + /* Must specify: * vertices: a list of vertices {x: x, y: y} in map gcs coordinates. * May specify: @@ -403,6 +484,7 @@ var polygonAnnotation = function (args) { fill: true, fillColor: {r: 0, g: 1, b: 0}, fillOpacity: 0.25, + polygon: function (d) { return d.polygon; }, stroke: true, strokeColor: {r: 0, g: 0, b: 0}, strokeOpacity: 1, @@ -414,8 +496,18 @@ var polygonAnnotation = function (args) { fill: true, fillColor: {r: 0.3, g: 0.3, b: 0.3}, fillOpacity: 0.25, + line: function (d) { + /* Return an array that has the same number of items as we have + * vertices. */ + return Array.apply(null, Array(m_this.options().vertices.length)).map( + function () { return d; }); + }, + polygon: function (d) { return d.polygon; }, + position: function (d, i) { + return m_this.options().vertices[i]; + }, stroke: false, - strokeColor: {r: 0, g: 0.843, b: 0}, + strokeColor: {r: 0, g: 0, b: 1}, strokeOpacity: 1, strokeWidth: 3, uniformPolygon: true diff --git a/src/feature.js b/src/feature.js index 39cbe275f4..cae1b1077b 100644 --- a/src/feature.js +++ b/src/feature.js @@ -380,7 +380,7 @@ var feature = function (arg) { */ this.featureGcsToDisplay = function (c) { var map = m_renderer.layer().map(); - c = map.gcsToWorld(c, map.ingcs()); + c = map.gcsToWorld(c, m_this.gcs()); c = map.worldToDisplay(c); if (m_renderer.baseToLocal) { c = m_renderer.baseToLocal(c); diff --git a/src/gl/lineFeature.js b/src/gl/lineFeature.js index 89bbf969d6..4ad76cbac0 100644 --- a/src/gl/lineFeature.js +++ b/src/gl/lineFeature.js @@ -146,6 +146,9 @@ var gl_lineFeature = function (arg) { for (i = 0; i < data.length; i += 1) { lineItem = m_this.line()(data[i], i); + if (lineItem.length < 2) { + continue; + } numSegments += lineItem.length - 1; for (j = 0; j < lineItem.length; j += 1) { pos = posFunc(lineItem[j], j, lineItem, i); @@ -188,6 +191,9 @@ var gl_lineFeature = function (arg) { for (i = posIdx3 = dest = dest3 = 0; i < data.length; i += 1) { lineItem = m_this.line()(data[i], i); + if (lineItem.length < 2) { + continue; + } firstPosIdx3 = posIdx3; for (j = 0; j < lineItem.length + (closed[i] === 2 ? 1 : 0); j += 1, posIdx3 += 3) { lidx = j; diff --git a/src/gl/polygonFeature.js b/src/gl/polygonFeature.js index 21f77810e1..443827e3c7 100644 --- a/src/gl/polygonFeature.js +++ b/src/gl/polygonFeature.js @@ -98,7 +98,7 @@ var gl_polygonFeature = function (arg) { * recalculate the style. */ function createGLPolygons(onlyStyle) { - var posBuf, posFunc, + var posBuf, posFunc, polyFunc, fillColor, fillColorFunc, fillColorVal, fillOpacity, fillOpacityFunc, fillOpacityVal, fillFunc, fillVal, @@ -121,11 +121,15 @@ var gl_polygonFeature = function (arg) { uniformPolyFunc = m_this.style.get('uniformPolygon'); if (!onlyStyle) { - posFunc = m_this.position(); + posFunc = m_this.style.get('position'); + polyFunc = m_this.style.get('polygon'); m_this.data().forEach(function (item, itemIndex) { var polygon, outer, geometry, c; - polygon = m_this.polygon()(item, itemIndex); + polygon = polyFunc(item, itemIndex); + if (!polygon) { + return; + } outer = polygon.outer || (polygon instanceof Array ? polygon : []); /* expand to an earcut polygon geometry. We had been using a map call, @@ -170,8 +174,10 @@ var gl_polygonFeature = function (arg) { item: item, itemIndex: itemIndex }; - items.push(record); - numPts += record.triangles.length; + if (record.triangles.length) { + items.push(record); + numPts += record.triangles.length; + } }); posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3); indices = geom.primitive(0).indices(); diff --git a/src/mapInteractor.js b/src/mapInteractor.js index d6c1a06a80..3db821e964 100644 --- a/src/mapInteractor.js +++ b/src/mapInteractor.js @@ -39,6 +39,8 @@ var mapInteractor = function (args) { m_selectionLayer = null, m_selectionQuad, m_paused = false, + // if m_clickMaybe is not false, it contains the x, y, and buttons that + // were present when the mouse down event occurred. m_clickMaybe = false, m_clickMaybeTimeout, m_callZoom = function () {}; @@ -652,7 +654,11 @@ var mapInteractor = function (args) { (!m_mouse.buttons.left || m_options.click.buttons.left) && (!m_mouse.buttons.right || m_options.click.buttons.right) && (!m_mouse.buttons.middle || m_options.click.buttons.middle)) { - m_this._setClickMaybe({x: m_mouse.page.x, y: m_mouse.page.y}); + m_this._setClickMaybe({ + x: m_mouse.page.x, + y: m_mouse.page.y, + buttons: $.extend({}, m_mouse.buttons) + }); if (m_options.click.duration > 0) { m_clickMaybeTimeout = window.setTimeout(function () { m_clickMaybe = false; @@ -1087,12 +1093,14 @@ var mapInteractor = function (args) { // unbind temporary handlers on document $(document).off('.geojs'); m_state.boundDocumentHandlers = false; + // add information about the button state to the event information + var details = m_this.mouse(); + details.buttonsDown = m_clickMaybe.buttons; // reset click detector variable m_this._setClickMaybe(false); - // fire a click event - m_this.map().geoTrigger(geo_event.mouseclick, m_this.mouse()); + m_this.map().geoTrigger(geo_event.mouseclick, details); }; //////////////////////////////////////////////////////////////////////////// diff --git a/src/polygonFeature.js b/src/polygonFeature.js index 7ea51078b2..c89f3aa630 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -58,8 +58,6 @@ var polygonFeature = function (arg) { */ //////////////////////////////////////////////////////////////////////////// var m_this = this, - m_position, - m_polygon, m_lineFeature, s_init = this._init, s_exit = this._exit, @@ -69,22 +67,6 @@ var polygonFeature = function (arg) { s_style = this.style, m_coordinates = []; - if (arg.polygon === undefined) { - m_polygon = function (d) { - return d; - }; - } else { - m_polygon = arg.polygon; - } - - if (arg.position === undefined) { - m_position = function (d) { - return d; - }; - } else { - m_position = arg.position; - } - //////////////////////////////////////////////////////////////////////////// /** * Get/set data. @@ -115,10 +97,13 @@ var polygonFeature = function (arg) { */ //////////////////////////////////////////////////////////////////////////// function getCoordinates() { - var posFunc = m_this.position(), - polyFunc = m_this.polygon(); + var posFunc = m_this.style.get('position'), + polyFunc = m_this.style.get('polygon'); m_coordinates = m_this.data().map(function (d, i) { var poly = polyFunc(d); + if (!poly) { + return; + } var outer, inner, range, coord, j, x, y; coord = poly.outer || (poly instanceof Array ? poly : []); @@ -164,9 +149,9 @@ var polygonFeature = function (arg) { //////////////////////////////////////////////////////////////////////////// this.polygon = function (val) { if (val === undefined) { - return m_polygon; + return m_this.style('polygon'); } else { - m_polygon = val; + m_this.style('polygon', val); m_this.dataTime().modified(); m_this.modified(); getCoordinates(); @@ -187,9 +172,9 @@ var polygonFeature = function (arg) { //////////////////////////////////////////////////////////////////////////// this.position = function (val) { if (val === undefined) { - return m_position; + return m_this.style('position'); } else { - m_position = val; + m_this.style('position', val); m_this.dataTime().modified(); m_this.modified(); getCoordinates(); @@ -294,12 +279,16 @@ var polygonFeature = function (arg) { } }); var data = this.data(), - posFunc = this.position(); + posFunc = this.style.get('position'), + polyFunc = this.style.get('polygon'); if (data !== m_lineFeature._lastData || posFunc !== m_lineFeature._posFunc) { var lineData = [], i, polygon, loop; for (i = 0; i < data.length; i += 1) { - polygon = m_this.polygon()(data[i], i); + polygon = polyFunc(data[i], i); + if (!polygon) { + continue; + } loop = polygon.outer || (polygon instanceof Array ? polygon : []); lineData.push(m_this._getLoopData(data[i], i, loop)); if (polygon.inner) { @@ -368,9 +357,10 @@ var polygonFeature = function (arg) { arg = arg || {}; s_init.call(m_this, arg); - var defaultStyle = $.extend( + var style = $.extend( {}, { + // default style fill: true, fillColor: {r: 0.0, g: 0.5, b: 0.5}, fillOpacity: 1.0, @@ -378,16 +368,21 @@ var polygonFeature = function (arg) { strokeWidth: 1.0, strokeStyle: 'solid', strokeColor: {r: 0.0, g: 1.0, b: 1.0}, - strokeOpacity: 1.0 + strokeOpacity: 1.0, + polygon: function (d) { return d; }, + position: function (d) { return d; } }, arg.style === undefined ? {} : arg.style ); - m_this.style(defaultStyle); - - if (m_position) { - m_this.dataTime().modified(); + if (arg.polygon !== undefined) { + style.polygon = arg.polygon; + } + if (arg.position !== undefined) { + style.position = arg.position; } + m_this.style(style); + this._checkForStroke(); }; diff --git a/tests/cases/polygonFeature.js b/tests/cases/polygonFeature.js index 8cc4964a94..990a25f806 100644 --- a/tests/cases/polygonFeature.js +++ b/tests/cases/polygonFeature.js @@ -84,8 +84,8 @@ describe('geo.polygonFeature', function () { polygon.position(function () { return 'b'; }); expect(polygon.position()('a')).toEqual('b'); - polygon = geo.polygonFeature({layer: layer, position: pos}); - polygon._init(); + polygon = geo.polygonFeature({layer: layer}); + polygon._init({position: pos}); expect(polygon.position()).toEqual(pos); }); @@ -100,8 +100,8 @@ describe('geo.polygonFeature', function () { polygon.polygon(function () { return 'b'; }); expect(polygon.polygon()('a')).toEqual('b'); - polygon = geo.polygonFeature({layer: layer, polygon: pos}); - polygon._init(); + polygon = geo.polygonFeature({layer: layer}); + polygon._init({polygon: pos}); expect(polygon.polygon()).toEqual(pos); });