1// This code is derived from the ISC-licensed "mapbox-gl-draw-freehand-mode" plugin 2// by Ben Ehmke, Eric Dong, and Joe Woodhouse. 3// Source: https://github.com/bemky/mapbox-gl-draw-freehand-mode 4(function (MapboxDraw) { 5 const { geojsonTypes, cursors, types, updateActions, modes, events } = 6 MapboxDraw.constants; 7 8 const FreehandMode = {}; 9 10 FreehandMode.onSetup = function () { 11 const polygon = this.newFeature({ 12 type: geojsonTypes.FEATURE, 13 properties: {}, 14 geometry: { 15 type: geojsonTypes.POLYGON, 16 coordinates: [[]], 17 }, 18 }); 19 20 this.addFeature(polygon); 21 this.clearSelectedFeatures(); 22 23 // Disable map dragging 24 setTimeout(() => { 25 if (!this.map || !this.map.dragPan) return; 26 this.map.dragPan.disable(); 27 }, 0); 28 29 this.updateUIClasses({ mouse: cursors.ADD }); 30 this.activateUIButton(types.POLYGON); 31 this.setActionableState({ 32 trash: true, 33 }); 34 35 return { 36 polygon, 37 currentVertexPosition: 0, 38 dragMoving: false, 39 isDrawing: false, 40 }; 41 }; 42 43 FreehandMode.onDrag = FreehandMode.onTouchMove = function (state, e) { 44 state.dragMoving = true; 45 state.isDrawing = true; 46 this.updateUIClasses({ mouse: cursors.ADD }); 47 state.polygon.updateCoordinate( 48 `0.${state.currentVertexPosition}`, 49 e.lngLat.lng, 50 e.lngLat.lat, 51 ); 52 state.currentVertexPosition++; 53 state.polygon.updateCoordinate( 54 `0.${state.currentVertexPosition}`, 55 e.lngLat.lng, 56 e.lngLat.lat, 57 ); 58 }; 59 60 FreehandMode.onMouseUp = function (state) { 61 if (state.dragMoving) { 62 this.simplify(state.polygon); 63 this.updateUIClasses({ mouse: cursors.MOVE }); 64 this.fireUpdate(); 65 this.changeMode(modes.SIMPLE_SELECT, { 66 featureIds: [state.polygon.id], 67 }); 68 } 69 }; 70 71 FreehandMode.fireCreate = function (polygon) { 72 this.map.fire(events.CREATE, { 73 features: [polygon.toGeoJSON()], 74 }); 75 }; 76 77 FreehandMode.fireUpdate = function () { 78 this.map.fire(events.UPDATE, { 79 action: updateActions.MOVE, 80 features: this.getSelected().map((f) => f.toGeoJSON()), 81 }); 82 }; 83 84 FreehandMode.simplify = function (polygon) { 85 if (!this.map.simplify_freehand) return; 86 87 const tolerance = 1 / Math.pow(1.05, 10 * this.map.getZoom()); 88 const simplifiedCoords = simplifyGeometry( 89 polygon.coordinates[0], 90 tolerance, 91 ); 92 polygon.setCoordinates([simplifiedCoords]); 93 }; 94 95 function simplifyGeometry(points, tolerance) { 96 if (points.length <= 2) return points; 97 98 const sqTolerance = tolerance * tolerance; 99 const last = points.length - 1; 100 const simplified = [points[0]]; 101 simplifyDPStep(points, 0, last, sqTolerance, simplified); 102 simplified.push(points[last]); 103 104 return simplified; 105 } 106 107 function simplifyDPStep(points, first, last, sqTolerance, simplified) { 108 let maxSqDist = sqTolerance; 109 let index; 110 111 for (let i = first + 1; i < last; i++) { 112 const sqDist = getSquareSegmentDistance( 113 points[i], 114 points[first], 115 points[last], 116 ); 117 if (sqDist > maxSqDist) { 118 index = i; 119 maxSqDist = sqDist; 120 } 121 } 122 123 if (maxSqDist > sqTolerance) { 124 if (index - first > 1) 125 simplifyDPStep(points, first, index, sqTolerance, simplified); 126 simplified.push(points[index]); 127 if (last - index > 1) 128 simplifyDPStep(points, index, last, sqTolerance, simplified); 129 } 130 } 131 132 function getSquareSegmentDistance(p, p1, p2) { 133 let x = p1[0], 134 y = p1[1]; 135 let dx = p2[0] - x, 136 dy = p2[1] - y; 137 138 if (dx !== 0 || dy !== 0) { 139 const t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy); 140 if (t > 1) { 141 x = p2[0]; 142 y = p2[1]; 143 } else if (t > 0) { 144 x += dx * t; 145 y += dy * t; 146 } 147 } 148 149 dx = p[0] - x; 150 dy = p[1] - y; 151 152 return dx * dx + dy * dy; 153 } 154 155 FreehandMode.onStop = function (state) { 156 this.updateUIClasses({ mouse: cursors.NONE }); 157 this.activateUIButton(); 158 159 // Enable map dragging 160 setTimeout(() => { 161 if (!this.map || !this.map.dragPan) return; 162 this.map.dragPan.enable(); 163 }, 0); 164 }; 165 166 FreehandMode.toDisplayFeatures = function (state, geojson, display) { 167 const isActivePolygon = geojson.properties.id === state.polygon.id; 168 geojson.properties.active = isActivePolygon ? "true" : "false";
169 if (!isActivePolygon) return display(geojson); 170 171 // Only render the polygon if it has at least three points 172 if (geojson.geometry.coordinates[0].length < 3) return; 173 174 const coordinateCount = geojson.geometry.coordinates[0].length; 175 176 // If we have fewer than three coordinates, we need to create a LineString instead of a Polygon 177 if (coordinateCount < 3) { 178 const lineCoordinates = [ 179 [ 180 geojson.geometry.coordinates[0][0][0], 181 geojson.geometry.coordinates[0][0][1], 182 ], 183 [ 184 geojson.geometry.coordinates[0][1][0], 185 geojson.geometry.coordinates[0][1][1], 186 ], 187 ]; 188 return display({ 189 type: geojsonTypes.FEATURE, 190 properties: geojson.properties, 191 geometry: { 192 coordinates: lineCoordinates, 193 type: geojsonTypes.LINE_STRING, 194 }, 195 }); 196 } 197 198 return display(geojson); 199 }; 200 201 FreehandMode.onTrash = function (state) { 202 this.deleteFeature([state.polygon.id], { silent: true }); 203 this.changeMode(modes.SIMPLE_SELECT); 204 }; 205 206 MapboxDraw.modes.draw_freehand = FreehandMode; 207})(MapboxDraw);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.