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); });