diff --git a/debug/app.js b/debug/app.js index c83df4a0c..a4c5da4d0 100644 --- a/debug/app.js +++ b/debug/app.js @@ -99,6 +99,12 @@ map.on('load', () => { Draw.changeMode('draw_polygon'); }; + // Jump into draw polygon mode via a custom UI element + const startLasso = document.getElementById('start-lasso'); + startLasso.onclick = function() { + Draw.changeMode('draw_lasso'); + }; + // Jump into static mode via a custom UI element const startStatic = document.getElementById('start-static'); startStatic.onclick = function() { diff --git a/debug/index.html b/debug/index.html index d62eaf1b7..a36706cb2 100644 --- a/debug/index.html +++ b/debug/index.html @@ -12,7 +12,7 @@ body { margin:0; padding:0; } html, body, #map { height: 100%; } .start-draw { - width: 315px; + width: 385px; position: absolute; left :10px; bottom: 10px; @@ -37,6 +37,7 @@
+
LASSO
STATIC
POINT
LINE
diff --git a/dist/mapbox-gl-draw.css b/dist/mapbox-gl-draw.css index d1da29cb4..8cf2e869c 100644 --- a/dist/mapbox-gl-draw.css +++ b/dist/mapbox-gl-draw.css @@ -39,6 +39,9 @@ .mapbox-gl-draw_polygon { background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJzcXVhcmUuc3ZnIj4gIDxkZWZzICAgICBpZD0iZGVmczE5MTY5IiAvPiAgPHNvZGlwb2RpOm5hbWVkdmlldyAgICAgaWQ9ImJhc2UiICAgICBwYWdlY29sb3I9IiNmZmZmZmYiICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6em9vbT0iMTEuMzEzNzA4IiAgICAgaW5rc2NhcGU6Y3g9IjExLjY4MTYzNCIgICAgIGlua3NjYXBlOmN5PSI5LjI4NTcxNDMiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIHVuaXRzPSJweCIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI4MCIgICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc1MSIgICAgIGlua3NjYXBlOndpbmRvdy14PSIwIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjIzIiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIgICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+ICAgIDxpbmtzY2FwZTpncmlkICAgICAgIHR5cGU9Inh5Z3JpZCIgICAgICAgaWQ9ImdyaWQxOTcxNSIgLz4gIDwvc29kaXBvZGk6bmFtZWR2aWV3PiAgPG1ldGFkYXRhICAgICBpZD0ibWV0YWRhdGExOTE3MiI+ICAgIDxyZGY6UkRGPiAgICAgIDxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+ICAgICAgICA8ZGM6dHlwZSAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4gICAgICAgIDxkYzp0aXRsZSAvPiAgICAgIDwvY2M6V29yaz4gICAgPC9yZGY6UkRGPiAgPC9tZXRhZGF0YT4gIDxnICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIgICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiICAgICBpZD0ibGF5ZXIxIiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTAzMi4zNjIyKSI+ICAgIDxwYXRoICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC41O21hcmtlcjpub25lO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGQ9Im0gNSwxMDM5LjM2MjIgMCw2IDIsMiA2LDAgMiwtMiAwLC02IC0yLC0yIC02LDAgeiBtIDMsMCA0LDAgMSwxIDAsNCAtMSwxIC00LDAgLTEsLTEgMCwtNCB6IiAgICAgICBpZD0icmVjdDc3OTciICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjY2NjY2NjIiAvPiAgICA8Y2lyY2xlICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS42MDAwMDAwMjttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBpZD0icGF0aDQzNjQiICAgICAgIGN4PSI2IiAgICAgICBjeT0iMTA0Ni4zNjIyIiAgICAgICByPSIyIiAvPiAgICA8Y2lyY2xlICAgICAgIGlkPSJwYXRoNDM2OCIgICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTt2aXNpYmlsaXR5OnZpc2libGU7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoxLjYwMDAwMDAyO21hcmtlcjpub25lO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGN4PSIxNCIgICAgICAgY3k9IjEwNDYuMzYyMiIgICAgICAgcj0iMiIgLz4gICAgPGNpcmNsZSAgICAgICBpZD0icGF0aDQzNzAiICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MS42MDAwMDAwMjttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBjeD0iNiIgICAgICAgY3k9IjEwMzguMzYyMiIgICAgICAgcj0iMiIgLz4gICAgPGNpcmNsZSAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO3Zpc2liaWxpdHk6dmlzaWJsZTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjEuNjAwMDAwMDI7bWFya2VyOm5vbmU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIgICAgICAgaWQ9InBhdGg0MzcyIiAgICAgICBjeD0iMTQiICAgICAgIGN5PSIxMDM4LjM2MjIiICAgICAgIHI9IjIiIC8+ICA8L2c+PC9zdmc+); } +.mapbox-gl-draw_lasso { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii02IC02IDMyIDMyIj48ZyB0cmFuc2Zvcm09Im1hdHJpeCguMDM5NjUgMCAwIC4wMzk2NSAxLjE2MyAxLjE2MykiIGZpbGw9IiM0ZDRkNGQiPjxwYXRoIGQ9Im0yNTAuMiA4MS42MWM5Ny40MyAwIDE3OS43NSA0My40MyAxNzkuNzUgOTQuODMgMCAxMi40NDktNC45MzQgMjQuNDQ5LTEzLjY0NSAzNS40NjVsMzUuNDAyIDEwLjQwNGM4LjYxMy0xNC4yMjcgMTMuNTMzLTI5LjYyOSAxMy41MzMtNDUuODY5IDAtNzIuOTY1LTk0LjQ2LTEzMC4xMi0yMTUuMDQtMTMwLjEyLTEyMC41NyAwLTIxNS4wNCA1Ny4xNi0yMTUuMDQgMTMwLjEyIDAgMjYuOTE4IDEyLjkzNiA1MS42NDMgMzUuMTkgNzIuMTctNi45NTEgNC41MDItMTMuNzU2IDEwLjUwMi0xOC44MzYgMTguOTg0LTEwLjQ1MyAxNy40NDktMTAuNjYgMzkuMDktLjY0NSA2NC4zNSA5LjQzMyAyMy43OTEgNy4xMjUgMzIuNTgyIDUuNjkzIDM1LjI0LTMuMzU0IDYuMzIyLTE4LjEzIDkuNTE0LTMyLjM4NSAxMi41OTYtMy40ODYuNzU4LTcuMDQgMS41MzEtMTAuNTgyIDIuMzcxLTkuNDg0IDIuMjQtMTUuMzUzIDExLjcyNS0xMy4xMyAyMS4yMyAxLjkwMiA4LjExMSA5LjE2NCAxMy41OTYgMTcuMTYgMTMuNTk2IDEuMzQgMCAyLjcwOS0uMTYgNC4wNjgtLjQ2NyAzLjMyLS43OTEgNi42NTYtMS41MTggOS45MzItMi4yMjcgMjEuMTEtNC41NjQgNDUuMDItOS43NDIgNTYuMDgtMzAuNDgyIDguNDg2LTE1LjkwMiA3LjE5NS0zNi41MTYtNC4wMy02NC44NS01LjcyNS0xNC40MzUtNi4zODUtMjUuNTY0LTEuOTQ3LTMzLjEgNC45NjUtOC40NTEgMTYuMTQtMTIuMjA3IDIyLjE5LTEzLjQ2NyAzNS43MDUgMTkuOTAyIDgyLjg1IDMyLjM1NCAxMzUuNTkgMzMuODY5bC0xMC41MDQtMzUuNzRjLTg3Ljg3LTUuNzU4LTE1OC41Ni00Ni40NS0xNTguNTYtOTQuMDcuMDAwMS01MS40IDgyLjMyLTk0LjgzIDE3OS43NS05NC44MyIvPjxwYXRoIGQ9Im00ODcuNTcgMjY5LjYzbC0yMjIuMDUtNjUuMjdjLTEuMTE1LS4zMzgtMi4yNDQtLjQ4Mi0zLjM3My0uNDgyLTMuMTEzIDAtNi4xNTggMS4yMjctOC40MzQgMy41LTMuMDk2IDMuMDgtNC4yNTggNy42MTMtMy4wMiAxMS43ODlsNjUuMjcgMjIyLjFjMS4zNCA0LjYxMyA1LjM1MiA3Ljk4MiAxMC4xNDMgOC41LjQ1My4wNDcuODkxLjA2NCAxLjMyMi4wNjQgNC4zMDkgMCA4LjMwNy0yLjMzOCAxMC40MzktNi4xNjJsNTQuMDgtOTguMDQgOTguMDMtNTQuMDljNC4yMjUtMi4zMjIgNi42MjktNi45NTEgNi4xLTExLjcyNy0uNTE0LTQuNzg3LTMuODc0LTguODA1LTguNTAyLTEwLjE3NiIvPjwvZz48L3N2Zz4=); +} .mapbox-gl-draw_line { background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJsaW5lLnN2ZyI+ICA8ZGVmcyAgICAgaWQ9ImRlZnMxOTE2OSIgLz4gIDxzb2RpcG9kaTpuYW1lZHZpZXcgICAgIGlkPSJiYXNlIiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiICAgICBib3JkZXJvcGFjaXR5PSIxLjAiICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIgICAgIGlua3NjYXBlOnpvb209IjE2IiAgICAgaW5rc2NhcGU6Y3g9IjEyLjg5ODc3NSIgICAgIGlua3NjYXBlOmN5PSI5LjU4OTAxNTIiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0idHJ1ZSIgICAgIHVuaXRzPSJweCIgICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI4MCIgICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc1MSIgICAgIGlua3NjYXBlOndpbmRvdy14PSIwIiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjIzIiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIgICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+ICAgIDxpbmtzY2FwZTpncmlkICAgICAgIHR5cGU9Inh5Z3JpZCIgICAgICAgaWQ9ImdyaWQxOTcxNSIgLz4gIDwvc29kaXBvZGk6bmFtZWR2aWV3PiAgPG1ldGFkYXRhICAgICBpZD0ibWV0YWRhdGExOTE3MiI+ICAgIDxyZGY6UkRGPiAgICAgIDxjYzpXb3JrICAgICAgICAgcmRmOmFib3V0PSIiPiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+ICAgICAgICA8ZGM6dHlwZSAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4gICAgICAgIDxkYzp0aXRsZSAvPiAgICAgIDwvY2M6V29yaz4gICAgPC9yZGY6UkRGPiAgPC9tZXRhZGF0YT4gIDxnICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIgICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiICAgICBpZD0ibGF5ZXIxIiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTAzMi4zNjIyKSI+ICAgIDxwYXRoICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MzttYXJrZXI6bm9uZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBkPSJtIDEzLjUsMTAzNS44NjIyIGMgLTEuMzgwNzEyLDAgLTIuNSwxLjExOTMgLTIuNSwyLjUgMCwwLjMyMDggMC4wNDYxNCwwLjYyNDQgMC4xNTYyNSwwLjkwNjMgbCAtMy43NSwzLjc1IGMgLTAuMjgxODM2LC0wLjExMDIgLTAuNTg1NDIxLC0wLjE1NjMgLTAuOTA2MjUsLTAuMTU2MyAtMS4zODA3MTIsMCAtMi41LDEuMTE5MyAtMi41LDIuNSAwLDEuMzgwNyAxLjExOTI4OCwyLjUgMi41LDIuNSAxLjM4MDcxMiwwIDIuNSwtMS4xMTkzIDIuNSwtMi41IDAsLTAuMzIwOCAtMC4wNDYxNCwtMC42MjQ0IC0wLjE1NjI1LC0wLjkwNjIgbCAzLjc1LC0zLjc1IGMgMC4yODE4MzYsMC4xMTAxIDAuNTg1NDIxLDAuMTU2MiAwLjkwNjI1LDAuMTU2MiAxLjM4MDcxMiwwIDIuNSwtMS4xMTkzIDIuNSwtMi41IDAsLTEuMzgwNyAtMS4xMTkyODgsLTIuNSAtMi41LC0yLjUgeiIgICAgICAgaWQ9InJlY3Q2NDY3IiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPiAgPC9nPjwvc3ZnPg==); } diff --git a/docs/API.md b/docs/API.md index 79695e808..d8dc680dc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -30,7 +30,7 @@ All of the following options are optional. - `boxSelect`, boolean (default `true`): Whether or not to enable box selection of features with `shift`+`click`+drag. If `false`, `shift`+`click`+drag zooms into an area. - `clickBuffer`, number (default: `2`): Number of pixels around any feature or vertex (in every direction) that will respond to a click. - `touchBuffer`, number (default: `25`): Number of pixels around any feature of vertex (in every direction) that will respond to a touch. -- `controls`, Object: Hide or show individual controls. Each property's name is a control, and value is a boolean indicating whether the control is on or off. Available control names are `point`, `line_string`, `polygon`, `trash`, `combine_features` and `uncombine_features`. By default, all controls are on. To change that default, use `displayControlsDefault`. +- `controls`, Object: Hide or show individual controls. Each property's name is a control, and value is a boolean indicating whether the control is on or off. Available control names are `point`, `line_string`, `polygon`, `lasso`, `trash`, `combine_features` and `uncombine_features`. By default, all controls are on. To change that default, use `displayControlsDefault`. - `displayControlsDefault`, boolean (default: `true`): The default value for `controls`. For example, if you would like all controls to be *off* by default, and specify an allowed list with `controls`, use `displayControlsDefault: false`. - `styles`, Array\: An array of map style objects. By default, Draw provides a map style for you. To learn about overriding styles, see the [Styling Draw](#styling-draw) section below. - `modes`, Object: over ride the default modes with your own. `MapboxDraw.modes` can be used to see the default values. More information on custom modes [can be found here](https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/MODES.md). @@ -75,6 +75,12 @@ Lets you draw a LineString feature. Lets you draw a Polygon feature. +### `draw_lasso` + +`Draw.modes.DRAW_LASSO === 'draw_lasso'` + +Lets you draw a Polygon feature with the Lasso tool. + ### `draw_point` `Draw.modes.DRAW_POINT === 'draw_point'` @@ -541,7 +547,7 @@ property | values | function --- | --- | --- meta | feature, midpoint, vertex | `midpoint` and `vertex` are used on points added to the map to communicate polygon and line handles. `feature` is used for all features. active | true, false | A feature is active when it is 'selected' in the current mode. `true` and `false` are strings. -mode | simple_select, direct_select, draw_point, draw_line_string, draw_polygon | Indicates which mode Draw is currently in. +mode | simple_select, direct_select, draw_point, draw_line_string, draw_polygon, draw_lasso | Indicates which mode Draw is currently in. Draw also provides a few more properties on features, but they should not be used for styling. For details on them, see "Using Draw with Mapbox GL JS's `queryRenderedFeatures`" below. diff --git a/package.json b/package.json index dc3097b53..61272e384 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@mapbox/geojson-normalize": "0.0.1", "@mapbox/geojsonhint": "3.0.0", "@mapbox/point-geometry": "0.1.0", + "@turf/simplify": "^5.1.5", "hat": "0.0.3", "lodash.isequal": "^4.2.0", "xtend": "^4.0.1" diff --git a/src/constants.js b/src/constants.js index 14cc3516f..a565edd4e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,6 +4,7 @@ export const classes = { CONTROL_BUTTON: 'mapbox-gl-draw_ctrl-draw-btn', CONTROL_BUTTON_LINE: 'mapbox-gl-draw_line', CONTROL_BUTTON_POLYGON: 'mapbox-gl-draw_polygon', + CONTROL_BUTTON_LASSO: 'mapbox-gl-draw_lasso', CONTROL_BUTTON_POINT: 'mapbox-gl-draw_point', CONTROL_BUTTON_TRASH: 'mapbox-gl-draw_trash', CONTROL_BUTTON_COMBINE_FEATURES: 'mapbox-gl-draw_combine', @@ -28,6 +29,7 @@ export const cursors = { }; export const types = { + LASSO: 'lasso', POLYGON: 'polygon', LINE: 'line_string', POINT: 'point' @@ -48,6 +50,7 @@ export const geojsonTypes = { export const modes = { DRAW_LINE_STRING: 'draw_line_string', DRAW_POLYGON: 'draw_polygon', + DRAW_LASSO: 'draw_lasso', DRAW_POINT: 'draw_point', SIMPLE_SELECT: 'simple_select', DIRECT_SELECT: 'direct_select', diff --git a/src/events.js b/src/events.js index 02936ac92..385cc6e9c 100644 --- a/src/events.js +++ b/src/events.js @@ -140,6 +140,8 @@ export default function(ctx) { changeMode(Constants.modes.DRAW_LINE_STRING); } else if (event.keyCode === 51 && ctx.options.controls.polygon) { changeMode(Constants.modes.DRAW_POLYGON); + } else if (event.keyCode === 83 && ctx.options.controls.lasso) { + changeMode(Constants.modes.DRAW_LASSO); } }; diff --git a/src/modes/draw_lasso.js b/src/modes/draw_lasso.js new file mode 100644 index 000000000..47ac2b4ef --- /dev/null +++ b/src/modes/draw_lasso.js @@ -0,0 +1,171 @@ +import turfSimplify from '@turf/simplify'; + +import * as CommonSelectors from '../lib/common_selectors'; +import doubleClickZoom from '../lib/double_click_zoom'; +import * as Constants from '../constants'; +import isEventAtCoordinates from '../lib/is_event_at_coordinates'; +import createVertex from '../lib/create_vertex'; + +const DrawLasso = {}; + +DrawLasso.onSetup = function() { + const polygon = this.newFeature({ + type: Constants.geojsonTypes.FEATURE, + properties: {}, + geometry: { + type: Constants.geojsonTypes.POLYGON, + coordinates: [[]] + } + }); + + this.addFeature(polygon); + + this.clearSelectedFeatures(); + doubleClickZoom.disable(this); + this.updateUIClasses({ mouse: Constants.cursors.ADD }); + this.activateUIButton(Constants.types.LASSO); + this.setActionableState({ + trash: true + }); + + return { + polygon, + currentVertexPosition: 0, + toggled: false + }; +}; + +DrawLasso.clickAnywhere = function(state, e) { + if (state.currentVertexPosition > 0 && isEventAtCoordinates(e, state.polygon.coordinates[0][state.currentVertexPosition - 1])) { + return this.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.polygon.id] }); + } +}; + +DrawLasso.clickOnVertex = function(state) { + return this.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.polygon.id] }); +}; + +DrawLasso.onMouseMove = function(state, e) { + if (!state.toggled) { + return; + } + + this.updateUIClasses({ mouse: Constants.cursors.ADD }); + state.polygon.updateCoordinate(`0.${state.currentVertexPosition}`, e.lngLat.lng, e.lngLat.lat); + state.currentVertexPosition++; + state.polygon.updateCoordinate(`0.${state.currentVertexPosition}`, e.lngLat.lng, e.lngLat.lat); +}; + +DrawLasso.onTap = DrawLasso.onClick = function(state, e) { + state.toggled = !state.toggled; + if (!state.toggled) { + // We have just finished drawing with lasso + let tolerance = (3 / ((this.map.getZoom() - 4) * 150)) - 0.001; // https://www.desmos.com/calculator/b3zi8jqskw + if (tolerance < 0 || !(isFinite(tolerance))) { + // Tolerance cannot be negative + tolerance = 0; + } + + // Simplify the polygon because mouseMove events result in tons of points that build up the resulting polygon + turfSimplify(state.polygon, { + tolerance, + mutate: true, + highQuality: true + }); + this.fireUpdate(); + + this.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.polygon.id] }); + } + if (CommonSelectors.isVertex(e)) return this.clickOnVertex(state, e); + return this.clickAnywhere(state, e); +}; + +DrawLasso.onKeyUp = function(state, e) { + if (CommonSelectors.isEscapeKey(e)) { + this.deleteFeature([state.polygon.id], { silent: true }); + this.changeMode(Constants.modes.SIMPLE_SELECT); + } else if (CommonSelectors.isEnterKey(e)) { + this.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.polygon.id] }); + } +}; + +DrawLasso.onStop = function(state) { + this.updateUIClasses({ mouse: Constants.cursors.NONE }); + doubleClickZoom.enable(this); + this.activateUIButton(); + + // check to see if we've deleted this feature + if (this.getFeature(state.polygon.id) === undefined) return; + + //remove last added coordinate + state.polygon.removeCoordinate(`0.${state.currentVertexPosition}`); + if (state.polygon.isValid()) { + this.map.fire(Constants.events.CREATE, { + features: [state.polygon.toGeoJSON()] + }); + } else { + this.deleteFeature([state.polygon.id], { silent: true }); + this.changeMode(Constants.modes.SIMPLE_SELECT, {}, { silent: true }); + } +}; + +DrawLasso.toDisplayFeatures = function(state, geojson, display) { + const isActiveLasso = geojson.properties.id === state.polygon.id; + geojson.properties.active = (isActiveLasso) ? Constants.activeStates.ACTIVE : Constants.activeStates.INACTIVE; + if (!isActiveLasso) return display(geojson); + + // Don't render a polygon until it has two positions + // (and a 3rd which is just the first repeated) + if (geojson.geometry.coordinates.length === 0) return; + + const coordinateCount = geojson.geometry.coordinates[0].length; + // 2 coordinates after selecting a draw type + // 3 after creating the first point + if (coordinateCount < 3) { + return; + } + geojson.properties.meta = Constants.meta.FEATURE; + display(createVertex(state.polygon.id, geojson.geometry.coordinates[0][0], '0.0', false)); + if (coordinateCount > 3) { + // Add a start position marker to the map, clicking on this will finish the feature + // This should only be shown when we're in a valid spot + const endPos = geojson.geometry.coordinates[0].length - 3; + display(createVertex(state.polygon.id, geojson.geometry.coordinates[0][endPos], `0.${endPos}`, false)); + } + if (coordinateCount <= 4) { + // If we've only drawn two positions (plus the closer), + // make a LineString instead of a Lasso + const lineCoordinates = [ + [geojson.geometry.coordinates[0][0][0], geojson.geometry.coordinates[0][0][1]], [geojson.geometry.coordinates[0][1][0], geojson.geometry.coordinates[0][1][1]] + ]; + // create an initial vertex so that we can track the first point on mobile devices + display({ + type: Constants.geojsonTypes.FEATURE, + properties: geojson.properties, + geometry: { + coordinates: lineCoordinates, + type: Constants.geojsonTypes.LINE_STRING + } + }); + if (coordinateCount === 3) { + return; + } + } + // render the Lasso + return display(geojson); +}; + +DrawLasso.onTrash = function(state) { + this.deleteFeature([state.polygon.id], { silent: true }); + this.changeMode(Constants.modes.SIMPLE_SELECT); +}; + + +DrawLasso.fireUpdate = function() { + this.map.fire(Constants.events.UPDATE, { + action: Constants.updateActions.MOVE, + features: this.getSelected().map(f => f.toGeoJSON()) + }); +}; + +export default DrawLasso; diff --git a/src/modes/index.js b/src/modes/index.js index 483ee7d26..4d7580e74 100644 --- a/src/modes/index.js +++ b/src/modes/index.js @@ -3,6 +3,7 @@ import simple_select from './simple_select'; import direct_select from './direct_select'; import draw_point from './draw_point'; import draw_polygon from './draw_polygon'; +import draw_lasso from './draw_lasso'; import draw_line_string from './draw_line_string'; export default { @@ -10,5 +11,6 @@ export default { direct_select, draw_point, draw_polygon, + draw_lasso, draw_line_string, }; diff --git a/src/options.js b/src/options.js index 720aa351a..2ed227238 100644 --- a/src/options.js +++ b/src/options.js @@ -22,6 +22,7 @@ const showControls = { point: true, line_string: true, polygon: true, + lasso: true, trash: true, combine_features: true, uncombine_features: true @@ -31,6 +32,7 @@ const hideControls = { point: false, line_string: false, polygon: false, + lasso: false, trash: false, combine_features: false, uncombine_features: false diff --git a/src/ui.js b/src/ui.js index 7add8d11c..d5fee9f35 100644 --- a/src/ui.js +++ b/src/ui.js @@ -125,6 +125,16 @@ export default function(ctx) { }); } + if (controls[Constants.types.LASSO]) { + buttonElements[Constants.types.LASSO] = createControlButton(Constants.types.LASSO, { + container: controlGroup, + className: Constants.classes.CONTROL_BUTTON_LASSO, + title: `Lasso tool ${ctx.options.keybindings ? '(s)' : ''}`, + onActivate: () => ctx.events.changeMode(Constants.modes.DRAW_LASSO), + onDeactivate: () => ctx.events.trash() + }); + } + if (controls[Constants.types.POINT]) { buttonElements[Constants.types.POINT] = createControlButton(Constants.types.POINT, { container: controlGroup, diff --git a/test/api.test.js b/test/api.test.js index a67732f37..77fd059bb 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -418,7 +418,7 @@ test('Draw.modes', (t) => { t.equal(Draw.modes.DRAW_LINE_STRING, Constants.modes.DRAW_LINE_STRING, 'draw_line_string'); t.equal(Draw.modes.DRAW_POLYGON, Constants.modes.DRAW_POLYGON, 'draw_polygon'); t.equal(Draw.modes.STATIC, Constants.modes.STATIC, 'static'); - t.equal(getPublicMemberKeys(Draw.modes).length, 6, 'no unexpected modes'); + t.equal(getPublicMemberKeys(Draw.modes).length, 7, 'no unexpected modes'); t.end(); }); diff --git a/test/options.test.js b/test/options.test.js index c4669eb67..b0c819607 100644 --- a/test/options.test.js +++ b/test/options.test.js @@ -22,6 +22,7 @@ test('Options test', (t) => { point: true, line_string: true, polygon: true, + lasso: true, trash: true, combine_features: true, uncombine_features: true @@ -49,6 +50,7 @@ test('Options test', (t) => { point: true, line_string: true, polygon: true, + lasso: true, trash: true, combine_features: true, uncombine_features: true @@ -76,6 +78,7 @@ test('Options test', (t) => { point: false, line_string: false, polygon: false, + lasso: false, trash: false, combine_features: false, uncombine_features: false @@ -102,6 +105,7 @@ test('Options test', (t) => { point: true, line_string: false, polygon: false, + lasso: false, trash: false, combine_features: false, uncombine_features: false @@ -129,6 +133,7 @@ test('Options test', (t) => { point: false, line_string: true, polygon: true, + lasso: true, trash: true, combine_features: true, uncombine_features: true @@ -156,6 +161,7 @@ test('Options test', (t) => { point: true, line_string: true, polygon: true, + lasso: true, trash: true, combine_features: true, uncombine_features: true diff --git a/yarn.lock b/yarn.lock index 445438fc2..29ada45c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -877,6 +877,21 @@ "@turf/helpers" "6.x" "@turf/meta" "6.x" +"@turf/clean-coords@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@turf/clean-coords/-/clean-coords-5.1.5.tgz#12800a98a78c9a452a72ec428493c43acf2ada1f" + integrity sha1-EoAKmKeMmkUqcuxChJPEOs8q2h8= + dependencies: + "@turf/helpers" "^5.1.5" + "@turf/invariant" "^5.1.5" + +"@turf/clone@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@turf/clone/-/clone-5.1.5.tgz#253e8d35477181976e33adfab50a0f02a7f0e367" + integrity sha1-JT6NNUdxgZduM636tQoPAqfw42c= + dependencies: + "@turf/helpers" "^5.1.5" + "@turf/combine@^3.14.0": version "3.14.0" resolved "https://registry.yarnpkg.com/@turf/combine/-/combine-3.14.0.tgz#d6aa15e20c7a217bf4dd19f0347ec848135cf222" @@ -894,6 +909,18 @@ resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-3.13.0.tgz#d06078a1464cf56cdb7ea624ea1e13a71b88b806" integrity sha1-0GB4oUZM9WzbfqYk6h4TpxuIuAY= +"@turf/helpers@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-5.1.5.tgz#153405227ab933d004a5bb9641a9ed999fcbe0cf" + integrity sha1-FTQFInq5M9AEpbuWQantmZ/L4M8= + +"@turf/invariant@^5.1.5": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-5.2.0.tgz#f0150ff7290b38577b73d088b7932c1ee0aa90a7" + integrity sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc= + dependencies: + "@turf/helpers" "^5.1.5" + "@turf/meta@6.x": version "6.0.2" resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-6.0.2.tgz#eb92951126d24a613ac1b7b99d733fcc20fd30cf" @@ -906,6 +933,23 @@ resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-3.14.0.tgz#8d3050c1a0f44bf406a633b6bd28c510f7bcee27" integrity sha1-jTBQwaD0S/QGpjO2vSjFEPe87ic= +"@turf/meta@^5.1.5": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-5.2.0.tgz#3b1ad485ee0c3b0b1775132a32c384d53e4ba53d" + integrity sha1-OxrUhe4MOwsXdRMqMsOE1T5LpT0= + dependencies: + "@turf/helpers" "^5.1.5" + +"@turf/simplify@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@turf/simplify/-/simplify-5.1.5.tgz#0ac8f27a2eb4218183edd9998c3275abe408b926" + integrity sha1-Csjyei60IYGD7dmZjDJ1q+QIuSY= + dependencies: + "@turf/clean-coords" "^5.1.5" + "@turf/clone" "^5.1.5" + "@turf/helpers" "^5.1.5" + "@turf/meta" "^5.1.5" + "@turf/union@^3.0.10": version "3.14.0" resolved "https://registry.yarnpkg.com/@turf/union/-/union-3.14.0.tgz#0a79180aeacf9c23c45b568a273e8bef02bc078f"