├── .DS_Store ├── .gitattributes ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── example ├── boat.png ├── index.css ├── index.html └── index.js ├── images ├── animation-smoothfactor-demo.gif ├── banner.PNG ├── color-1.PNG ├── color-2.PNG ├── color-3.PNG ├── fill-1.PNG ├── fill-2.PNG ├── fill-3.PNG ├── frequency-1.PNG ├── frequency-2.PNG ├── frequency-3.gif ├── frequency-4.gif ├── frequency-5.PNG ├── offset-both-1.png ├── offset-end-1.png ├── offset-end-2.png ├── perHatOptions-1.png ├── perHatOptions-2.png ├── size-1.gif ├── yawn-1.PNG ├── yawn-2.PNG ├── yawn-3.PNG └── yawn-4.PNG ├── npm.README.md ├── package-lock.json ├── package.json └── src ├── index.d.ts ├── index.js └── leaflet-arrowheads.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | #Folders 2 | images/ 3 | example/ 4 | .vscode/ 5 | 6 | #Files 7 | src/index.css 8 | src/index.html 9 | src/index.js 10 | .gitignore 11 | .gitattributes 12 | .npmignore 13 | .prettierrc -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 slutske22 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leaflet-arrowheads 2 | 3 | Leaflet-Arrowheads is a small plugin for leaflet to quickly draw arrowheads on polylines for vector visualization. 4 | 5 |

6 | 7 |

8 | 9 |

10 | 👀 DEMO 👀 11 |

12 | 13 | ## Installation 14 | 15 | Leaflet-Arrowheads compatible with leaflet 1.7.1+. It has 2 dependencies: [Leaflet](https://leafletjs.com/) itself, and [Leaflet GeometryUtil](https://github.com/makinacorpus/Leaflet.GeometryUtil). 16 | 17 | You can use npm to install leaflet-arrowheads: 18 | 19 | ``` 20 | npm install leaflet-arrowheads --save 21 | ``` 22 | 23 | Then you can simply import its content into your project: 24 | 25 | ```javascript 26 | import 'leaflet-arrowheads'; 27 | ``` 28 | 29 | ### Without ES6 Imports 30 | 31 | Grab the [source file](https://github.com/slutske22/leaflet-arrowheads/blob/master/src/leaflet-arrowheads.js) and include it in your project. You can include the source file in your header, but it must come _after_ a link to [Leaflet GeometryUtil](https://github.com/makinacorpus/Leaflet.GeometryUtil), which must come _after_ a link to the leaflet source. Your main project javascript will come after this, like so: 32 | 33 | ```html 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | ## Usage 43 | 44 | Arrowheads can be applied to any polyline, whether unisegmental, multisegmental, continuous, or discontinuous: 45 | 46 | ```javascript 47 | var myVector = L.polyline([coords]).arrowheads(); 48 | ``` 49 | 50 | Arrowheads will be added to your polyline and will automatically be added to and removed from the map when you call add and remove methods on your polyline: 51 | 52 | ```javascript 53 | myVector.addTo(map) or myVector.remove() 54 | ``` 55 | 56 | If you need to access the arrowheads directly, you can call the `.getArrowheads()` method on your polyline. 57 | 58 | ```javascript 59 | myVector.getArrowheads(); // returns the arrowheads polyline object 60 | myVector.getArrowheads().remove(); // removes arrowheads from map 61 | ``` 62 | 63 | Arrowheads can also be deleted from their parent polyline entirely: 64 | 65 | ```javascript 66 | myVector.deleteArrowheads(); 67 | ``` 68 | 69 | Arrowheads can take a configuration object as its argument: 70 | 71 | ```javascript 72 | var myVector = L.polyline([ coords ]).arrowheads({ }); 73 | ``` 74 | 75 | You can also use arrowheads on a GeoJSON that contains `LineString` or `MultiLineString` features by adding it as an option: 76 | 77 | ```javascript 78 | var myGeoJson = L.geoJSON(geoJsonData, { arrowheads: { } }); 79 | ``` 80 | 81 | ## Options 82 | 83 | Arrowheads offers a variety of options for rendering and styling arrowheads. See the options table below.
84 |
85 | Arrowheads inherit all options from [L.Path](https://leafletjs.com/reference-1.6.0.html#path). Arrowheads also inherit all options from their parent polylines, except `fill`, `fillOpacity`, and `smoothFactor`. These can be changed manually when defining the arrowheads' options, but changing smoothFactor will result in improperly rendered arrows.
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 110 | 111 | 118 | 119 | 120 | 121 | 122 | 123 | 125 | 126 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
Option Type Default Description
yawn Number ( Degrees ) 60 Defines the width of the opening of the arrowhead, given in degrees. The larger the angle, the wider the arrowhead.
size String
109 | ( Meters or Percent or Pixels )
'15%' Determines the size of the arrowhead. Accepts three types of values:
112 |
    113 |
  • A string value which is a number with the suffix 'm' ( '1.5m', '20m', '250m', etc. ) will render arrows whose size is that many metres. Ideal for maps with low variance in zoom levels.
  • 114 |
  • A string value which is a number with a percent sign ( '15%', '20%', '25%', etc. ) will render arrows whose size is that percentage of the size of the parent polyline. If the polyline has multiple segments, 'size' will take the percent of the average size of the segments.
  • 115 |
  • A string value which is a number with the suffix 'px' ( '20px', '25px', '30px', etc. ) will render an arrowhead whose size stays at a constant pixel value, regardless of zoom level. Will look strange at low zoom levels or for smaller parent vectors. Ideal for larger parent vectors and at higher zoom levels.
  • 116 |
117 |
frequency Number | String
124 | ( Number of arrowheads | Meters, Pixels, 'allvertices', 'endonly' )
'allvertices' How many arrowheads are rendered on a polyline. 127 |
    128 |
  • 'allvertices' renders an arrowhead on each vertex.
  • 129 |
  • 'endonly' renders only one at the end.
  • 130 |
  • A number value renders that number of arrowheads evenly spaces across the polyline.
  • 131 |
  • A string value with suffix 'm' (i.e. '100m') will render arrowheads spaced evenly along the polyline with roughly that many meters between each one.
  • 132 |
  • A string value with suffix 'px' (i.e. '30px') will render arrowheads spaced evenly with roughly that many pixels between each, regardless of zoom level.
  • 133 |
134 |
proportionalToTotal Boolean false Only relevant when size is given as a percent. Useful when frequency is set to 'endonly'. Will render the arrowhead(s) with a size proportional to the entire length of the multi-segmented polyline, rather than proportional to the average length of all the segments.
offsets Object
{
start?: string;
end?: string
}
undefined Enables the developer to have the arrowheads start or end at some offset from the start and/or end of the polyline. This option can contain one or both start and end properties. Each must be a string defining the size of the offset in either meters or pixels (i.e. '100m', '15px', etc.)
perArrowheadOptions Function
(i: number) => ArrowheadOptions
undefined Enables the developer to customize arrowheads on a one-by-one basis. Must be in the form of a function of i, which is the index of the arrowhead as it is rendered in the loop through all arrowheads. Must return an object that is options object, the same type of options object that is the agrument for .arrowheads({ <Options> }). Cannnot account for frequency or proportionalToTotal from within the perArrowheadOptions callback. See examples for details.

162 |
163 | There are many different ways to combine these various options. See examles below. Many combinations are untested, so if you encounter a problem, open and issue or a PR. 164 | 165 | ## Examples 166 | 167 | A demo project is available for viewing at https://codesandbox.io/s/leaflet-arrowheads-example-zfxxc. 168 | The web page alone without the code: https://zfxxc.csb.app/ 169 | 170 | There is also a small typescript example sandbox [here](https://codesandbox.io/s/leaflet-arrowheads-ts-example-kwonf). 171 | 172 | Polylines in this demo have popups which each contain the code for that polyline. Click around, and feel free to look through the codesandbox for more detail. 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 184 | 187 | 192 | 195 | 196 | 197 | 198 | 201 | 202 | 206 | 207 | 208 |
Yawn Options
181 |
L.polyline([]).arrowheads()
182 | (Standard option gives 60 degree yawn) 183 |
185 | 186 | 188 |
L.polyline([]).arrowheads({
189 |   yawn: 90
190 | })
191 |
193 | 194 |
L.polyline([]).arrowheads({
199 |   yawn: 40
200 | })
.arrowheads({
203 |   yawn: 40,
204 |   fill: true
205 | })
209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 220 | 223 | 228 | 231 | 232 | 233 | 234 | 237 | 238 | 244 | 245 | 246 | 247 | 248 | 251 | 252 | 257 | 258 | 259 |
Color and Fill Options
217 |
L.polyline([]).arrowheads()

218 | (Standard options makes arrowheads a vector with same color as parent) 219 |
221 | 222 | 224 |
L.polyline([]).arrowheads({
225 |   fill: true
226 | })
227 |
229 | 230 |
L.polyline([]).arrowheads({
235 |   color: 'black'
236 | })
L.polyline([],{
239 |   color: 'black'
240 | })
241 |     .arrowheads({
242 |        fill: true
243 |     })
L.polyline([]).arrowheads({
249 |   color: 'black'
250 | })
L.polyline([]).arrowheads({
253 |   fill: true,
254 |   color: 'black'
255 |   fillColor: 'green'
256 | })
260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 274 | 277 | 278 |
Size Options
Setting size to a number or percent will give you a fixed size arrowhead (in meters or percent of the size of the segment, respectively), regardless of zoom size. See the frequency examples below for a better idea.
272 |
L.polyline([coords]).arrowheads({size: '20px', fill: true})
273 |
275 | 276 |
279 | 280 | 281 | 282 | 283 | 290 | 291 | 299 | 300 | 305 | 306 | 311 | 312 | 321 |
Frequency Options
284 | Standard option: 285 |
L.polyline([coords], { smoothFactor: 5 })
286 |    .arrowheads({ frequency: 'allvertices' });
287 |
288 | 289 |
292 |
L.polyline([coords])
293 |    .arrowheads({ 
294 |       frequency: 'endonly', 
295 |       size: '50%' 
296 |    });
297 | 298 |
301 | 20 arrowheads evenly distributed 302 |
L.polyline([coords]).arrowheads({ frequency: 20 });
303 | 304 |
307 | Arrowheads every ~500 m evenly distributed 308 |
L.polyline([coords]).arrowheads({ frequency: '500m' });
309 | 310 |
313 | Arrowheads every 50px regardless of zoom 314 |
L.polyline([coords])
315 |    .arrowheads({ 
316 |       frequency: '50px', 
317 |       size: '12px'
318 |    });
319 | 320 |
322 | 323 | 324 | 325 | 326 | 335 | 336 | 344 | 345 | 356 |
Offset Options
327 |
L.polyline([coords])
328 |    .arrowheads({ 
329 |       frequency: 'endonly',
330 |       size: '30px',
331 |       offsets: { end: '15px' }
332 |    });
333 | 334 |
337 |
L.polyline([coords])
338 |    .arrowheads({ frequency: 20,
339 |       size: '300m',
340 |       offsets: { end: '15px' }
341 |    });
342 | 343 |
346 |
L.polyline([coords1, coords2])
347 |    .arrowheads({ frequency: '1000m',
348 |       size: '300m',
349 |       offsets: { 
350 |          start: '5000m', 
351 |          end: '15px' 
352 |       }
353 |    });
354 | 355 |
357 | 358 | 359 | 360 | 361 | 362 | 376 | 377 | 378 | 379 | 392 |
Per-Arrowhead Options
363 |
L.polyline([coords], { color: 'black', weight: '2' })
364 |    .arrowheads({
365 |       frequency: '500m',
366 |       color: 'darkblue',
367 |       perArrowheadOptions: (i) => ({
368 |          size: i % 3 === 0 ? '30%' : '15%',
369 |          color: i % 2 === 0 ? 'red' : undefined,
370 |          fill: (i + 1) % 4 === 0,
371 |          yawn: (i + 1) % 4 === 0 ? 35 : undefined,
372 |       }),
373 |    });
374 | 375 |
380 |
L.polyline([coords])
381 |    .arrowheads({ 
382 |       size: '20px',
383 |       fill: true,
384 |       yawn: 30,
385 |       frequency: 20,
386 |       perArrowheadOptions: (i) => ({
387 |          color: `rgba(150, 20, ${0 + 20 * i}, 1)`,
388 |       }),
389 |    });
390 | 391 |
393 | 394 | ## Alternatives 395 | 396 | After writing this plugin I discovered [Leaflet.PolylineDecorator](https://github.com/bbecquet/Leaflet.PolylineDecorator). This offers some great methods to decorate your lines, potentially with arrowheads. 397 | 398 | ## Limitations 399 | 400 | Arrowheads sometimes look like they're in slightly the wrong orientation in areas of high curvature. This is because of the way leaflet-arrowheads chooses and interpolates the points that it uses to calculate bearings. This may be able to be improved. Feel free to contribute / open a PR. 401 | -------------------------------------------------------------------------------- /example/boat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/example/boat.png -------------------------------------------------------------------------------- /example/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #mapID { 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | .norwayLink { 11 | color: #0078a8; 12 | } 13 | 14 | .norwayLink:hover { 15 | text-decoration: underline; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leaflet Arrowheads 6 | 7 | 11 | 12 | 13 | 19 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var mapOptions = { 2 | center: [32.02556114475524, -119.78169059753418], 3 | zoom: 9, 4 | wheelPxPerZoomLevel: 100, 5 | }; 6 | 7 | const map = L.map('mapID', mapOptions); 8 | 9 | map.on('popupopen', () => { 10 | hljs.highlightAll(); 11 | }); 12 | 13 | const myLayer = new L.TileLayer( 14 | 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 15 | { 16 | attribution: 17 | 'Map data © OpenStreetMap contributors', 18 | } 19 | ); 20 | 21 | myLayer.addTo(map); 22 | 23 | var boatIcon = L.icon({ 24 | iconUrl: 'boat.png', 25 | iconSize: [32, 32], 26 | shadowSize: [0, 0], 27 | iconAnchor: [16, 16], 28 | shadowAnchor: [4, 62], 29 | popupAnchor: [-3, -76], 30 | }); 31 | 32 | // ------- GROUP 1: Arrowhead Color, Fill, and Yawn Options --------------// 33 | 34 | let group1Offsetx = 0.4, 35 | group1Offsety = 0.55; 36 | 37 | var simpleVector0 = L.polyline( 38 | [ 39 | [ 40 | map.getCenter().lat - 0.05 + group1Offsetx, 41 | map.getCenter().lng - group1Offsety - 0.25, 42 | ], 43 | [ 44 | map.getCenter().lat + 0.05 + group1Offsetx, 45 | map.getCenter().lng - group1Offsety - 0.25 + 0.05, 46 | ], 47 | ], 48 | { color: 'black' } 49 | ) 50 | .arrowheads() 51 | .bindPopup(`
L.polyline(coords).arrowheads()
`, { 52 | maxWidth: 2000, 53 | minWidth: 400, 54 | }); 55 | 56 | var simpleVector1 = L.polyline( 57 | [ 58 | [ 59 | map.getCenter().lat - 0.05 + group1Offsetx, 60 | map.getCenter().lng - group1Offsety - 0.15, 61 | ], 62 | [ 63 | map.getCenter().lat + 0.05 + group1Offsetx, 64 | map.getCenter().lng - group1Offsety - 0.15 + 0.05, 65 | ], 66 | ], 67 | { color: 'blue', weight: 2 } 68 | ) 69 | .arrowheads() 70 | .bindPopup( 71 | `
L.polyline(coords, { color: 'blue', weight: 2 })
 72 |   .arrowheads();
`, 73 | { maxWidth: 2000, minWidth: 400 } 74 | ); 75 | 76 | var simpleVector2 = L.polyline( 77 | [ 78 | [ 79 | map.getCenter().lat - 0.05 + group1Offsetx, 80 | map.getCenter().lng - group1Offsety - 0.05, 81 | ], 82 | [ 83 | map.getCenter().lat + 0.05 + group1Offsetx, 84 | map.getCenter().lng - group1Offsety - 0.05 + 0.05, 85 | ], 86 | ], 87 | { color: 'purple' } 88 | ) 89 | .arrowheads({ fill: true }) 90 | .bindPopup( 91 | `
var simpleVector1 = L.polyline(coords)
 92 |   .arrowheads({ fill: true });
`, 93 | { maxWidth: 2000, minWidth: 400 } 94 | ); 95 | 96 | var simpleVector3 = L.polyline( 97 | [ 98 | [ 99 | map.getCenter().lat - 0.05 + group1Offsetx, 100 | map.getCenter().lng - group1Offsety + 0.05, 101 | ], 102 | [ 103 | map.getCenter().lat + 0.05 + group1Offsetx, 104 | map.getCenter().lng - group1Offsety + 0.05 + 0.05, 105 | ], 106 | ], 107 | { color: 'green' } 108 | ) 109 | .arrowheads({ color: 'black' }) 110 | .bindPopup( 111 | `
L.polyline(coords, { color: 'green' })
112 |   .arrowheads({ color: 'black' })
`, 113 | { maxWidth: 2000, minWidth: 400 } 114 | ); 115 | 116 | var simpleVector4 = L.polyline( 117 | [ 118 | [ 119 | map.getCenter().lat - 0.05 + group1Offsetx, 120 | map.getCenter().lng - group1Offsety + 0.15, 121 | ], 122 | [ 123 | map.getCenter().lat + 0.05 + group1Offsetx, 124 | map.getCenter().lng - group1Offsety + 0.15 + 0.05, 125 | ], 126 | ], 127 | { color: 'orange', weight: 2 } 128 | ) 129 | .arrowheads({ yawn: 40 }) 130 | .bindPopup( 131 | `
L.polyline(coords, { color: 'orange', weight: 2 })
132 |   .arrowheads({ yawn: 40 });
`, 133 | { maxWidth: 2000, minWidth: 400 } 134 | ); 135 | 136 | var simpleVector5 = L.polyline( 137 | [ 138 | [ 139 | map.getCenter().lat - 0.05 + group1Offsetx, 140 | map.getCenter().lng - group1Offsety + 0.25, 141 | ], 142 | [ 143 | map.getCenter().lat + 0.05 + group1Offsetx, 144 | map.getCenter().lng - group1Offsety + 0.25 + 0.05, 145 | ], 146 | ], 147 | { color: 'red' } 148 | ) 149 | .arrowheads({ fill: true, color: 'purple' }) 150 | .bindPopup( 151 | `
var simpleVector1 = L.polyline()
152 |   .arrowheads({ fill: true, color: 'purple' });
`, 153 | { maxWidth: 2000, minWidth: 400 } 154 | ); 155 | 156 | var group1 = L.layerGroup([ 157 | simpleVector0, 158 | simpleVector1, 159 | simpleVector2, 160 | simpleVector3, 161 | simpleVector4, 162 | simpleVector5, 163 | ]); 164 | 165 | group1.addTo(map); 166 | 167 | // ------- GROUP 2: Arrowhead Size Options------------------------// 168 | 169 | var group2offsetx = 0.6; 170 | var group2offsety = 0.05; 171 | 172 | var multiVector1 = L.polyline( 173 | [ 174 | [ 175 | [ 176 | map.getCenter().lat - 0.2 + group2offsety, 177 | map.getCenter().lng - 0.4 + group2offsetx, 178 | ], 179 | [ 180 | map.getCenter().lat - 0.1 + group2offsety, 181 | map.getCenter().lng - 0.5 + group2offsetx, 182 | ], 183 | [ 184 | map.getCenter().lat + 0.0 + group2offsety, 185 | map.getCenter().lng - 0.4 + group2offsetx, 186 | ], 187 | ], 188 | [ 189 | [ 190 | map.getCenter().lat + 0.0 + group2offsety, 191 | map.getCenter().lng - 0.5 + group2offsetx, 192 | ], 193 | [ 194 | map.getCenter().lat + 0.1 + group2offsety, 195 | map.getCenter().lng - 0.4 + group2offsetx, 196 | ], 197 | ], 198 | ], 199 | { smoothFactor: 30 } 200 | ) 201 | .arrowheads() 202 | .bindPopup(`
L.polyline(coords).arrowheads()
`, { 203 | maxWidth: 2000, 204 | minWidth: 400, 205 | }); 206 | 207 | var multiVector2 = L.polyline( 208 | [ 209 | [ 210 | [ 211 | map.getCenter().lat - 0.2 + group2offsety, 212 | map.getCenter().lng - 0.4 + group2offsetx + 0.15, 213 | ], 214 | [ 215 | map.getCenter().lat - 0.1 + group2offsety, 216 | map.getCenter().lng - 0.5 + group2offsetx + 0.15, 217 | ], 218 | [ 219 | map.getCenter().lat + 0.0 + group2offsety, 220 | map.getCenter().lng - 0.4 + group2offsetx + 0.15, 221 | ], 222 | ], 223 | [ 224 | [ 225 | map.getCenter().lat + 0.0 + group2offsety, 226 | map.getCenter().lng - 0.5 + group2offsetx + 0.15, 227 | ], 228 | [ 229 | map.getCenter().lat + 0.1 + group2offsety, 230 | map.getCenter().lng - 0.4 + group2offsetx + 0.15, 231 | ], 232 | ], 233 | ], 234 | { smoothFactor: 30, color: 'black' } 235 | ) 236 | .arrowheads({ size: '10%', frequency: 'endonly' }) 237 | .bindPopup( 238 | `
L.polyline(coords)
239 |   .arrowheads({ 
240 |     size: '10%', 
241 |     frequency: 'endonly' 
242 |   });
`, 243 | { maxWidth: 2000, minWidth: 400 } 244 | ); 245 | 246 | var multiVector3 = L.polyline( 247 | [ 248 | [ 249 | [ 250 | map.getCenter().lat - 0.2 + group2offsety, 251 | map.getCenter().lng - 0.4 + group2offsetx + 0.3, 252 | ], 253 | [ 254 | map.getCenter().lat - 0.1 + group2offsety, 255 | map.getCenter().lng - 0.5 + group2offsetx + 0.3, 256 | ], 257 | [ 258 | map.getCenter().lat + 0.0 + group2offsety, 259 | map.getCenter().lng - 0.4 + group2offsetx + 0.3, 260 | ], 261 | ], 262 | [ 263 | [ 264 | map.getCenter().lat + 0.0 + group2offsety, 265 | map.getCenter().lng - 0.5 + group2offsetx + 0.3, 266 | ], 267 | [ 268 | map.getCenter().lat + 0.1 + group2offsety, 269 | map.getCenter().lng - 0.4 + group2offsetx + 0.3, 270 | ], 271 | ], 272 | ], 273 | { smoothFactor: 30, color: 'purple' } 274 | ) 275 | .arrowheads({ size: '10%', frequency: '3000m' }) 276 | .bindPopup( 277 | `
L.polyline(coords)
278 |   .arrowheads({
279 |     size: '10%', 
280 |     frequency: '3000m'
281 |   });
`, 282 | { maxWidth: 2000, minWidth: 400 } 283 | ); 284 | 285 | var multiVector4 = L.polyline( 286 | [ 287 | [ 288 | [ 289 | map.getCenter().lat - 0.2 + group2offsety, 290 | map.getCenter().lng - 0.4 + group2offsetx + 0.45, 291 | ], 292 | [ 293 | map.getCenter().lat - 0.1 + group2offsety, 294 | map.getCenter().lng - 0.5 + group2offsetx + 0.45, 295 | ], 296 | [ 297 | map.getCenter().lat + 0.0 + group2offsety, 298 | map.getCenter().lng - 0.4 + group2offsetx + 0.45, 299 | ], 300 | ], 301 | [ 302 | [ 303 | map.getCenter().lat + 0.0 + group2offsety, 304 | map.getCenter().lng - 0.5 + group2offsetx + 0.45, 305 | ], 306 | [ 307 | map.getCenter().lat + 0.1 + group2offsety, 308 | map.getCenter().lng - 0.4 + group2offsetx + 0.45, 309 | ], 310 | ], 311 | ], 312 | { smoothFactor: 30, color: 'green' } 313 | ) 314 | .arrowheads({ size: '10%', frequency: '50px', fill: true, yawn: 30 }) 315 | .bindPopup( 316 | `
L.polyline(coords)
317 |   .arrowheads({
318 |     size: '10%',
319 |     frequency: '50px', 
320 |     fill: true, 
321 |     yawn: 30
322 |   });
`, 323 | { maxWidth: 2000, minWidth: 400 } 324 | ); 325 | 326 | var multiVector5 = L.polyline( 327 | [ 328 | [ 329 | [ 330 | map.getCenter().lat - 0.2 + group2offsety, 331 | map.getCenter().lng - 0.4 + group2offsetx + 0.6, 332 | ], 333 | [ 334 | map.getCenter().lat - 0.1 + group2offsety, 335 | map.getCenter().lng - 0.5 + group2offsetx + 0.6, 336 | ], 337 | [ 338 | map.getCenter().lat + 0.0 + group2offsety, 339 | map.getCenter().lng - 0.4 + group2offsetx + 0.6, 340 | ], 341 | ], 342 | [ 343 | [ 344 | map.getCenter().lat + 0.0 + group2offsety, 345 | map.getCenter().lng - 0.5 + group2offsetx + 0.6, 346 | ], 347 | [ 348 | map.getCenter().lat + 0.1 + group2offsety, 349 | map.getCenter().lng - 0.4 + group2offsetx + 0.6, 350 | ], 351 | ], 352 | ], 353 | { smoothFactor: 30, color: 'darkred' } 354 | ) 355 | .arrowheads({ size: '15px', frequency: '50px', fill: true, yawn: 30 }) 356 | .bindPopup( 357 | `
L.polyline(coords)
358 |   .arrowheads({
359 |     size: '15px', 
360 |     frequency: '50px', 
361 |     fill: true, 
362 |     yawn: 30
363 |   });
`, 364 | { maxWidth: 2000, minWidth: 400 } 365 | ); 366 | 367 | var group2 = L.layerGroup([ 368 | multiVector1, 369 | multiVector2, 370 | multiVector3, 371 | multiVector4, 372 | multiVector5, 373 | ]); 374 | 375 | group2.addTo(map); 376 | 377 | // ------- GROUP 3: Arrowhead frequency options --------------------// 378 | 379 | var malibuPathPoints = [ 380 | [34.047112447489766, -118.92691612243652], 381 | [34.047112447489766, -118.92549991607666], 382 | [34.04675685986366, -118.92356872558594], 383 | [34.044907780170604, -118.91721725463866], 384 | [34.04369874472491, -118.9106512069702], 385 | [34.042560813265304, -118.90176773071289], 386 | [34.04156511071315, -118.89490127563475], 387 | [34.04060495789538, -118.88764858245848], 388 | [34.04000041165585, -118.88121128082274], 389 | [34.03964479420797, -118.87314319610594], 390 | [34.0392536132932, -118.86962413787842], 391 | [34.03822230950643, -118.86610507965086], 392 | [34.036550859499016, -118.86129856109618], 393 | [34.03601741107435, -118.85477542877196], 394 | [34.03434591762097, -118.84988307952881], 395 | [34.032852214755096, -118.84584903717041], 396 | [34.029722466330576, -118.8416004180908], 397 | [34.0259879563598, -118.83649349212645], 398 | [34.02381830842462, -118.8325881958008], 399 | [34.02221771291966, -118.8301420211792], 400 | [34.0193721352679, -118.82670879364015], 401 | [34.017095604460124, -118.8238763809204], 402 | [34.01659760520752, -118.8218593597412], 403 | [34.016455319170184, -118.81941318511961], 404 | [34.016917747919564, -118.8172674179077], 405 | [34.01791373974716, -118.8151216506958], 406 | [34.02061708722915, -118.81190299987793], 407 | [34.0221110054794, -118.80911350250244], 408 | [34.02239555835533, -118.80722522735596], 409 | [34.02221771291966, -118.80542278289794], 410 | [34.02161303565095, -118.8028049468994], 411 | [34.0208305057319, -118.79988670349121], 412 | [34.02043923806691, -118.79726886749268], 413 | [34.0206882267897, -118.79533767700195], 414 | [34.02139961911581, -118.79306316375731], 415 | [34.023000230049014, -118.78945827484131], 416 | [34.024351833551606, -118.78602504730225], 417 | [34.02556114475524, -118.78169059753418], 418 | [34.02602352389661, -118.77890110015869], 419 | [34.02591682124144, -118.7761116027832], 420 | [34.02559671247093, -118.7734079360962], 421 | [34.02552557702461, -118.76958847045898], 422 | [34.025774550825844, -118.76602649688719], 423 | [34.02623692880446, -118.76263618469237], 424 | [34.0281575488251, -118.75825881958009], 425 | [34.02982916420039, -118.7550401687622], 426 | [34.031109528172735, -118.75061988830566], 427 | [34.03256769694176, -118.74555587768553], 428 | [34.03320786068001, -118.7425947189331], 429 | [34.03335011863251, -118.739333152771], 430 | [34.03356350511403, -118.73602867126465], 431 | [34.03335011863251, -118.73285293579102], 432 | [34.03370576247019, -118.72967720031738], 433 | [34.034096968969656, -118.72624397277832], 434 | [34.034132533107424, -118.72246742248535], 435 | [34.03388358382995, -118.71881961822511], 436 | [34.03377689105882, -118.7151288986206], 437 | [34.034239225431264, -118.71053695678711], 438 | [34.0346659933849, -118.70547294616698], 439 | ]; 440 | 441 | var group3offsetx = 0.2, 442 | group3offsety = -0.1; 443 | 444 | var path1points = malibuPathPoints.map((point) => { 445 | return [point[0] + group3offsety - 1.8, point[1] + group3offsetx - 1.7]; 446 | }); 447 | 448 | var path2points = malibuPathPoints.map((point) => { 449 | return [point[0] + group3offsety - 1.85, point[1] + group3offsetx - 1.7]; 450 | }); 451 | 452 | var path3points = malibuPathPoints.map((point) => { 453 | return [point[0] + group3offsety - 1.9, point[1] + group3offsetx - 1.7]; 454 | }); 455 | 456 | var path4points = malibuPathPoints.map((point) => { 457 | return [point[0] + group3offsety - 1.95, point[1] + group3offsetx - 1.7]; 458 | }); 459 | 460 | var path5points = malibuPathPoints.map((point) => { 461 | return [point[0] + group3offsety - 2, point[1] + group3offsetx - 1.7]; 462 | }); 463 | 464 | var path6points = malibuPathPoints.map((point) => { 465 | return [point[0] + group3offsety - 2.05, point[1] + group3offsetx - 1.7]; 466 | }); 467 | 468 | // Offset path points: 469 | var path7points = malibuPathPoints.map((point) => { 470 | return [point[0] + group3offsety - 2.15, point[1] + group3offsetx - 1.7]; 471 | }); 472 | 473 | var path8points = malibuPathPoints.map((point) => { 474 | return [point[0] + group3offsety - 2.23, point[1] + group3offsetx - 1.7]; 475 | }); 476 | 477 | var path9points = [ 478 | malibuPathPoints.map((point) => { 479 | return [point[0] + group3offsety - 2.3, point[1] + group3offsetx - 1.7]; 480 | }), 481 | malibuPathPoints.map((point) => { 482 | return [point[0] + group3offsety - 2.275, point[1] + group3offsetx - 1.7]; 483 | }), 484 | ]; 485 | 486 | var path1 = L.polyline([path1points], { smoothFactor: 1.5, weight: 2 }) 487 | .arrowheads() 488 | .bindPopup(`
L.polyline(coords).arrowheads({})
`, { 489 | maxWidth: 2000, 490 | minWidth: 400, 491 | }); 492 | 493 | var path2 = L.polyline([path2points], { smoothFactor: 1.5, weight: 2 }) 494 | .arrowheads({ size: '50%', frequency: 'endonly' }) 495 | .bindPopup( 496 | `
L.polyline(coords)
497 |   .arrowheads({ 
498 |     size: '50%', 
499 |     frequency: 'endonly' 
500 |   });
`, 501 | { maxWidth: 2000, minWidth: 400 } 502 | ); 503 | 504 | var path3 = L.polyline([path3points], { smoothFactor: 1.5, weight: 2 }) 505 | .arrowheads({ size: '25%', frequency: 'endonly', proportionalToTotal: true }) 506 | .bindPopup( 507 | `
var path3 = L.polyline(coords)
508 |   .arrowheads({ 
509 |     size: '25%', 
510 |     frequency: 'endonly', 
511 |     proportionalToTotal: true
512 |   });
`, 513 | { maxWidth: 2000, minWidth: 400 } 514 | ); 515 | 516 | var path4 = L.polyline([path4points], { smoothFactor: 1.5, weight: 2 }) 517 | .arrowheads({ 518 | frequency: '500m', 519 | color: 'darkblue', 520 | perArrowheadOptions: (i) => ({ 521 | size: i % 3 === 0 ? '30%' : '15%', 522 | color: i % 2 === 0 ? 'red' : undefined, 523 | fill: (i + 1) % 4 === 0, 524 | yawn: (i + 1) % 4 === 0 ? 35 : undefined, 525 | }), 526 | }) 527 | .bindPopup( 528 | `
var path4 = L.polyline(coords)
529 |   .arrowheads({ 
530 |     frequency: '500m',
531 |     color: 'darkblue',
532 |     perArrowheadOptions: (i) => ({ 
533 |       color: i % 2 === 0 ? 'red' : undefined, 
534 |       size: i % 3 === 0 ? '30%' : '15%',
535 |       fill: (i + 1) % 4 === 0,
536 |       yawn: (i + 1) % 4 === 0 ? 35 : undefined,
537 |     })
538 |   });
`, 539 | { maxWidth: 2000, minWidth: 400 } 540 | ); 541 | 542 | var path5 = L.polyline([path5points], { smoothFactor: 1.5, weight: 2 }) 543 | .arrowheads({ frequency: '50px' }) 544 | .bindPopup( 545 | `
L.polyline(coords).arrowheads({ frequency: '50px' });
`, 546 | { maxWidth: 2000, minWidth: 450 } 547 | ); 548 | 549 | var path6 = L.polyline([path6points], { smoothFactor: 1.5, weight: 2 }) 550 | .arrowheads({ size: '15px', frequency: '50px' }) 551 | .bindPopup( 552 | `
L.polyline(coords)
553 |   .arrowheads({ 
554 |     size: '15px', 
555 |     frequency: '50px' 
556 |   });
`, 557 | { maxWidth: 2000, minWidth: 400 } 558 | ); 559 | 560 | // Offset paths 561 | var path7 = L.polyline([path7points], { 562 | smoothFactor: 1.5, 563 | weight: 2, 564 | color: 'darkblue', 565 | }) 566 | .arrowheads({ 567 | size: '300m', 568 | frequency: 20, 569 | offsets: { end: '15px' }, 570 | }) 571 | .bindPopup( 572 | `
L.polyline(coords)
573 |   .arrowheads({ 
574 |     size: '300m', 
575 |     frequency: 20, 
576 |     offsets: { 
577 |       end: '15px' 
578 |     } 
579 |   });
`, 580 | { maxWidth: 2000, minWidth: 400 } 581 | ); 582 | 583 | var path8 = L.polyline([path8points[0], path8points[path8points.length - 1]], { 584 | smoothFactor: 1.5, 585 | weight: 2, 586 | color: 'darkblue', 587 | }) 588 | .arrowheads({ 589 | frequency: 'endonly', 590 | size: '30px', 591 | offsets: { end: '15px' }, 592 | }) 593 | .bindPopup( 594 | `
L.polyline(coords)
595 |   .arrowheads({ 
596 |     size: '30px', 
597 |     frequency: 'endonly', 
598 |     offsets: { 
599 |       end: '15px' 
600 |     } 
601 |   });
`, 602 | { maxWidth: 2000, minWidth: 400 } 603 | ); 604 | 605 | L.marker(path7points[path7points.length - 1], { 606 | icon: boatIcon, 607 | draggable: true, 608 | }).addTo(map); 609 | L.marker(path8points[path8points.length - 1], { 610 | icon: boatIcon, 611 | draggable: true, 612 | }).addTo(map); 613 | 614 | var path9 = L.polyline(path9points, { 615 | smoothFactor: 1.5, 616 | weight: 2, 617 | color: 'darkblue', 618 | }) 619 | .arrowheads({ 620 | frequency: '1000m', 621 | size: '300m', 622 | offsets: { start: '5000m', end: '10px' }, 623 | }) 624 | .bindPopup( 625 | `
L.polyline(coords)
626 |   .arrowheads({ 
627 |     size: '300m', 
628 |     frequency: '1000m', 
629 |     offsets: { 
630 |       start: '5000m', 
631 |       end: '10px' 
632 |     } 
633 |   });
`, 634 | { maxWidth: 2000, minWidth: 400 } 635 | ); 636 | 637 | var group3 = L.layerGroup([path1, path2, path3, path4, path5, path6]); 638 | var group4 = L.layerGroup([path7, path8, path9]); 639 | 640 | group3.addTo(map); 641 | group4.addTo(map); 642 | 643 | // ------- GROUP 1: Arrowhead Color, Fill, and Yawn Options --------------// 644 | 645 | var bigVector0 = L.polyline( 646 | [ 647 | [59.85688529423247, 10.491943359375], 648 | [59.94985301711567, 10.817413330078125], 649 | ], 650 | { color: 'black', stroke: '2' } 651 | ).arrowheads({ fill: false, yawn: 30 }); 652 | 653 | var bigVector1 = L.polyline( 654 | [ 655 | [58.401711667608, 5.6689453125], 656 | [60.261617082844616, 10.8544921875], 657 | ], 658 | { color: 'blue', weight: 2 } 659 | ).arrowheads({ fill: false, yawn: 30 }); 660 | 661 | var bigVector2 = L.polyline( 662 | [ 663 | [47.57652571374621, -27.333984375], 664 | [61.227957176677876, 10.810546875], 665 | ], 666 | { color: 'purple', weight: '2' } 667 | ).arrowheads({ size: '20px', fill: false, yawn: 30 }); 668 | 669 | var bigVector3 = L.polyline( 670 | [ 671 | [31.203404950917395, -84.0234375], 672 | [62.34960927573042, 10.8984375], 673 | ], 674 | { color: 'black', weight: '2' } 675 | ) 676 | .arrowheads({ 677 | size: '20px', 678 | fill: true, 679 | yawn: 30, 680 | frequency: 20, 681 | perArrowheadOptions: (i) => ({ 682 | color: `rgba(150, 20, ${0 + 20 * i}, 1)`, 683 | }), 684 | }) 685 | .bindPopup( 686 | `
L.polyline(coords)
687 |   .arrowheads({ 
688 |     size: '20px',
689 |     fill: true,
690 |     yawn: 30,
691 |     frequency: 20,
692 |     perArrowheadOptions: (i) => ({
693 |       color: \`rgba(150, 20, \${0 + 20 * i}, 1)\`,
694 |     }),
695 |   });
`, 696 | { maxWidth: 2000, minWidth: 400 } 697 | ); 698 | 699 | var groupBig = L.layerGroup([bigVector0, bigVector1, bigVector2, bigVector3]); 700 | 701 | groupBig.addTo(map); 702 | 703 | const somePopup = L.marker(map.getCenter()).bindPopup(` 704 | 705 | `); 706 | 707 | somePopup.addTo(map); 708 | 709 | function flyToNorway() { 710 | map.flyTo([47.57652571374621, -27.333984375], 3, { 711 | animate: true, 712 | duration: 5, 713 | }); 714 | 715 | somePopup.closePopup(); 716 | } 717 | 718 | // GeoJSON Examples: 719 | 720 | const samplegeojson = { 721 | type: 'FeatureCollection', 722 | features: [ 723 | { 724 | type: 'Feature', 725 | properties: {}, 726 | geometry: { 727 | type: 'LineString', 728 | coordinates: [ 729 | [-119.94323730468749, 31.487235582017444], 730 | [-119.92401123046875, 31.508312698943445], 731 | [-119.89654541015624, 31.529385064020936], 732 | [-119.85260009765624, 31.541089879585808], 733 | [-119.81689453125, 31.54577139493626], 734 | [-119.75372314453125, 31.548112064557003], 735 | [-119.7015380859375, 31.53640812943961], 736 | [-119.66583251953124, 31.52470272697062], 737 | [-119.62188720703125, 31.508312698943445], 738 | [-119.5806884765625, 31.49191979634118], 739 | [-119.52850341796875, 31.477866449675865], 740 | [-119.47631835937499, 31.47083898476439], 741 | [-119.41589355468749, 31.47083898476439], 742 | [-119.34722900390625, 31.48020882071693], 743 | [-119.2950439453125, 31.49426181553272], 744 | ], 745 | }, 746 | }, 747 | { 748 | type: 'Feature', 749 | properties: {}, 750 | geometry: { 751 | type: 'LineString', 752 | coordinates: [ 753 | [-119.44885253906251, 31.512995857454676], 754 | [-119.39941406249999, 31.52470272697062], 755 | [-119.33624267578124, 31.53640812943961], 756 | [-119.278564453125, 31.5504526754715], 757 | [-119.22088623046875, 31.55981453201843], 758 | [-119.1412353515625, 31.56449510799119], 759 | [-119.08355712890625, 31.555133721172034], 760 | [-119.02862548828125, 31.53640812943961], 761 | [-118.98468017578125, 31.512995857454676], 762 | [-118.91326904296874, 31.501287521196705], 763 | [-118.86383056640625, 31.512995857454676], 764 | [-118.80889892578126, 31.53640812943961], 765 | [-118.75396728515625, 31.573855555238104], 766 | [-118.73199462890626, 31.608948861695676], 767 | ], 768 | }, 769 | }, 770 | ], 771 | }; 772 | 773 | const geojsonExample = L.geoJSON(samplegeojson, { arrowheads: { size: '25%' } }) 774 | .bindPopup( 775 | `
L.geoJSON(sampleGeoJSON, {
776 |   arrowheads: { 
777 |     size: '25%' 
778 |   } 
779 | });
`, 780 | { minWidth: 400 } 781 | ) 782 | .addTo(map); 783 | 784 | // ------- Layers control --------------// 785 | 786 | L.control 787 | .layers(null, { 788 | 'Vector 1': simpleVector0, 789 | 'Vector 2': simpleVector1, 790 | 'Vector 3': simpleVector2, 791 | 'Vector 4': simpleVector3, 792 | 'Vector 5': simpleVector4, 793 | 'Vector 6': simpleVector5, 794 | 'Group 2': group2, 795 | 'Group 3': group3, 796 | GeoJSON: geojsonExample, 797 | }) 798 | .addTo(map); 799 | -------------------------------------------------------------------------------- /images/animation-smoothfactor-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/animation-smoothfactor-demo.gif -------------------------------------------------------------------------------- /images/banner.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/banner.PNG -------------------------------------------------------------------------------- /images/color-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/color-1.PNG -------------------------------------------------------------------------------- /images/color-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/color-2.PNG -------------------------------------------------------------------------------- /images/color-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/color-3.PNG -------------------------------------------------------------------------------- /images/fill-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/fill-1.PNG -------------------------------------------------------------------------------- /images/fill-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/fill-2.PNG -------------------------------------------------------------------------------- /images/fill-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/fill-3.PNG -------------------------------------------------------------------------------- /images/frequency-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/frequency-1.PNG -------------------------------------------------------------------------------- /images/frequency-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/frequency-2.PNG -------------------------------------------------------------------------------- /images/frequency-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/frequency-3.gif -------------------------------------------------------------------------------- /images/frequency-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/frequency-4.gif -------------------------------------------------------------------------------- /images/frequency-5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/frequency-5.PNG -------------------------------------------------------------------------------- /images/offset-both-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/offset-both-1.png -------------------------------------------------------------------------------- /images/offset-end-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/offset-end-1.png -------------------------------------------------------------------------------- /images/offset-end-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/offset-end-2.png -------------------------------------------------------------------------------- /images/perHatOptions-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/perHatOptions-1.png -------------------------------------------------------------------------------- /images/perHatOptions-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/perHatOptions-2.png -------------------------------------------------------------------------------- /images/size-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/size-1.gif -------------------------------------------------------------------------------- /images/yawn-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/yawn-1.PNG -------------------------------------------------------------------------------- /images/yawn-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/yawn-2.PNG -------------------------------------------------------------------------------- /images/yawn-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/yawn-3.PNG -------------------------------------------------------------------------------- /images/yawn-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slutske22/leaflet-arrowheads/ded413300e7d1cdfb459324120a2c25b6494f54e/images/yawn-4.PNG -------------------------------------------------------------------------------- /npm.README.md: -------------------------------------------------------------------------------- 1 | # leaflet-arrowheads 2 | Leaflet-Arrowheads is a small plugin for leaflet to quickly draw vector hats on polylines for vector visualization. 3 | 4 | More info at https://github.com/slutske22/leaflet-arrowheads 5 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-arrowheads", 3 | "version": "1.3.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "leaflet-arrowheads", 9 | "version": "1.3.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "leaflet": "^1.7.1", 13 | "leaflet-geometryutil": "^0.10.0" 14 | }, 15 | "devDependencies": { 16 | "@types/leaflet": "^1.7.9", 17 | "npm-run-all": "^4.1.5" 18 | } 19 | }, 20 | "node_modules/@types/geojson": { 21 | "version": "7946.0.8", 22 | "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", 23 | "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", 24 | "dev": true 25 | }, 26 | "node_modules/@types/leaflet": { 27 | "version": "1.7.9", 28 | "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.9.tgz", 29 | "integrity": "sha512-H8vPgD49HKzqM41ArHGZM70g/tfhp8W+JcPxfnF+5H/Xvp+xiP+KQOUNWU8U89fqS1Jj3cpRY/+nbnaHFzwnFA==", 30 | "dev": true, 31 | "dependencies": { 32 | "@types/geojson": "*" 33 | } 34 | }, 35 | "node_modules/ansi-styles": { 36 | "version": "3.2.1", 37 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 38 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 39 | "dev": true, 40 | "dependencies": { 41 | "color-convert": "^1.9.0" 42 | }, 43 | "engines": { 44 | "node": ">=4" 45 | } 46 | }, 47 | "node_modules/balanced-match": { 48 | "version": "1.0.0", 49 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 50 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 51 | "dev": true 52 | }, 53 | "node_modules/brace-expansion": { 54 | "version": "1.1.11", 55 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 56 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 57 | "dev": true, 58 | "dependencies": { 59 | "balanced-match": "^1.0.0", 60 | "concat-map": "0.0.1" 61 | } 62 | }, 63 | "node_modules/chalk": { 64 | "version": "2.4.2", 65 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 66 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 67 | "dev": true, 68 | "dependencies": { 69 | "ansi-styles": "^3.2.1", 70 | "escape-string-regexp": "^1.0.5", 71 | "supports-color": "^5.3.0" 72 | }, 73 | "engines": { 74 | "node": ">=4" 75 | } 76 | }, 77 | "node_modules/color-convert": { 78 | "version": "1.9.3", 79 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 80 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 81 | "dev": true, 82 | "dependencies": { 83 | "color-name": "1.1.3" 84 | } 85 | }, 86 | "node_modules/color-name": { 87 | "version": "1.1.3", 88 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 89 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 90 | "dev": true 91 | }, 92 | "node_modules/concat-map": { 93 | "version": "0.0.1", 94 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 95 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 96 | "dev": true 97 | }, 98 | "node_modules/cross-spawn": { 99 | "version": "6.0.5", 100 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 101 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 102 | "dev": true, 103 | "dependencies": { 104 | "nice-try": "^1.0.4", 105 | "path-key": "^2.0.1", 106 | "semver": "^5.5.0", 107 | "shebang-command": "^1.2.0", 108 | "which": "^1.2.9" 109 | }, 110 | "engines": { 111 | "node": ">=4.8" 112 | } 113 | }, 114 | "node_modules/define-properties": { 115 | "version": "1.1.3", 116 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 117 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 118 | "dev": true, 119 | "dependencies": { 120 | "object-keys": "^1.0.12" 121 | }, 122 | "engines": { 123 | "node": ">= 0.4" 124 | } 125 | }, 126 | "node_modules/error-ex": { 127 | "version": "1.3.2", 128 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 129 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 130 | "dev": true, 131 | "dependencies": { 132 | "is-arrayish": "^0.2.1" 133 | } 134 | }, 135 | "node_modules/es-abstract": { 136 | "version": "1.17.0", 137 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0.tgz", 138 | "integrity": "sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==", 139 | "dev": true, 140 | "dependencies": { 141 | "es-to-primitive": "^1.2.1", 142 | "function-bind": "^1.1.1", 143 | "has": "^1.0.3", 144 | "has-symbols": "^1.0.1", 145 | "is-callable": "^1.1.5", 146 | "is-regex": "^1.0.5", 147 | "object-inspect": "^1.7.0", 148 | "object-keys": "^1.1.1", 149 | "object.assign": "^4.1.0", 150 | "string.prototype.trimleft": "^2.1.1", 151 | "string.prototype.trimright": "^2.1.1" 152 | }, 153 | "engines": { 154 | "node": ">= 0.4" 155 | }, 156 | "funding": { 157 | "url": "https://github.com/sponsors/ljharb" 158 | } 159 | }, 160 | "node_modules/es-to-primitive": { 161 | "version": "1.2.1", 162 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 163 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 164 | "dev": true, 165 | "dependencies": { 166 | "is-callable": "^1.1.4", 167 | "is-date-object": "^1.0.1", 168 | "is-symbol": "^1.0.2" 169 | }, 170 | "engines": { 171 | "node": ">= 0.4" 172 | }, 173 | "funding": { 174 | "url": "https://github.com/sponsors/ljharb" 175 | } 176 | }, 177 | "node_modules/escape-string-regexp": { 178 | "version": "1.0.5", 179 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 180 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 181 | "dev": true, 182 | "engines": { 183 | "node": ">=0.8.0" 184 | } 185 | }, 186 | "node_modules/function-bind": { 187 | "version": "1.1.1", 188 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 189 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 190 | "dev": true 191 | }, 192 | "node_modules/graceful-fs": { 193 | "version": "4.2.3", 194 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 195 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", 196 | "dev": true 197 | }, 198 | "node_modules/has": { 199 | "version": "1.0.3", 200 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 201 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 202 | "dev": true, 203 | "dependencies": { 204 | "function-bind": "^1.1.1" 205 | }, 206 | "engines": { 207 | "node": ">= 0.4.0" 208 | } 209 | }, 210 | "node_modules/has-flag": { 211 | "version": "3.0.0", 212 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 213 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 214 | "dev": true, 215 | "engines": { 216 | "node": ">=4" 217 | } 218 | }, 219 | "node_modules/has-symbols": { 220 | "version": "1.0.1", 221 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 222 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 223 | "dev": true, 224 | "engines": { 225 | "node": ">= 0.4" 226 | }, 227 | "funding": { 228 | "url": "https://github.com/sponsors/ljharb" 229 | } 230 | }, 231 | "node_modules/hosted-git-info": { 232 | "version": "2.8.9", 233 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 234 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", 235 | "dev": true 236 | }, 237 | "node_modules/is-arrayish": { 238 | "version": "0.2.1", 239 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 240 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 241 | "dev": true 242 | }, 243 | "node_modules/is-callable": { 244 | "version": "1.1.5", 245 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", 246 | "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", 247 | "dev": true, 248 | "engines": { 249 | "node": ">= 0.4" 250 | }, 251 | "funding": { 252 | "url": "https://github.com/sponsors/ljharb" 253 | } 254 | }, 255 | "node_modules/is-date-object": { 256 | "version": "1.0.2", 257 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 258 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 259 | "dev": true, 260 | "engines": { 261 | "node": ">= 0.4" 262 | }, 263 | "funding": { 264 | "url": "https://github.com/sponsors/ljharb" 265 | } 266 | }, 267 | "node_modules/is-regex": { 268 | "version": "1.0.5", 269 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", 270 | "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", 271 | "dev": true, 272 | "dependencies": { 273 | "has": "^1.0.3" 274 | }, 275 | "engines": { 276 | "node": ">= 0.4" 277 | }, 278 | "funding": { 279 | "url": "https://github.com/sponsors/ljharb" 280 | } 281 | }, 282 | "node_modules/is-symbol": { 283 | "version": "1.0.3", 284 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 285 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 286 | "dev": true, 287 | "dependencies": { 288 | "has-symbols": "^1.0.1" 289 | }, 290 | "engines": { 291 | "node": ">= 0.4" 292 | }, 293 | "funding": { 294 | "url": "https://github.com/sponsors/ljharb" 295 | } 296 | }, 297 | "node_modules/isexe": { 298 | "version": "2.0.0", 299 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 300 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 301 | "dev": true 302 | }, 303 | "node_modules/json-parse-better-errors": { 304 | "version": "1.0.2", 305 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 306 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 307 | "dev": true 308 | }, 309 | "node_modules/leaflet": { 310 | "version": "1.7.1", 311 | "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", 312 | "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" 313 | }, 314 | "node_modules/leaflet-geometryutil": { 315 | "version": "0.10.0", 316 | "resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.10.0.tgz", 317 | "integrity": "sha512-4OGu2OnpHLx+7QpOY6NatrAiSWWrP/Z3HOM9ZsmQ0JWIzAytFS6SLUnKHVjwpgRzPUpV/6w0b4Fh24pVIgqFHw==", 318 | "dependencies": { 319 | "leaflet": "^1.6.0" 320 | } 321 | }, 322 | "node_modules/load-json-file": { 323 | "version": "4.0.0", 324 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 325 | "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", 326 | "dev": true, 327 | "dependencies": { 328 | "graceful-fs": "^4.1.2", 329 | "parse-json": "^4.0.0", 330 | "pify": "^3.0.0", 331 | "strip-bom": "^3.0.0" 332 | }, 333 | "engines": { 334 | "node": ">=4" 335 | } 336 | }, 337 | "node_modules/memorystream": { 338 | "version": "0.3.1", 339 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 340 | "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", 341 | "dev": true, 342 | "engines": { 343 | "node": ">= 0.10.0" 344 | } 345 | }, 346 | "node_modules/minimatch": { 347 | "version": "3.0.4", 348 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 349 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 350 | "dev": true, 351 | "dependencies": { 352 | "brace-expansion": "^1.1.7" 353 | }, 354 | "engines": { 355 | "node": "*" 356 | } 357 | }, 358 | "node_modules/nice-try": { 359 | "version": "1.0.5", 360 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 361 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 362 | "dev": true 363 | }, 364 | "node_modules/normalize-package-data": { 365 | "version": "2.5.0", 366 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 367 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 368 | "dev": true, 369 | "dependencies": { 370 | "hosted-git-info": "^2.1.4", 371 | "resolve": "^1.10.0", 372 | "semver": "2 || 3 || 4 || 5", 373 | "validate-npm-package-license": "^3.0.1" 374 | } 375 | }, 376 | "node_modules/npm-run-all": { 377 | "version": "4.1.5", 378 | "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", 379 | "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", 380 | "dev": true, 381 | "dependencies": { 382 | "ansi-styles": "^3.2.1", 383 | "chalk": "^2.4.1", 384 | "cross-spawn": "^6.0.5", 385 | "memorystream": "^0.3.1", 386 | "minimatch": "^3.0.4", 387 | "pidtree": "^0.3.0", 388 | "read-pkg": "^3.0.0", 389 | "shell-quote": "^1.6.1", 390 | "string.prototype.padend": "^3.0.0" 391 | }, 392 | "bin": { 393 | "npm-run-all": "bin/npm-run-all/index.js", 394 | "run-p": "bin/run-p/index.js", 395 | "run-s": "bin/run-s/index.js" 396 | }, 397 | "engines": { 398 | "node": ">= 4" 399 | } 400 | }, 401 | "node_modules/object-inspect": { 402 | "version": "1.7.0", 403 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", 404 | "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", 405 | "dev": true, 406 | "funding": { 407 | "url": "https://github.com/sponsors/ljharb" 408 | } 409 | }, 410 | "node_modules/object-keys": { 411 | "version": "1.1.1", 412 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 413 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 414 | "dev": true, 415 | "engines": { 416 | "node": ">= 0.4" 417 | } 418 | }, 419 | "node_modules/object.assign": { 420 | "version": "4.1.0", 421 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 422 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 423 | "dev": true, 424 | "dependencies": { 425 | "define-properties": "^1.1.2", 426 | "function-bind": "^1.1.1", 427 | "has-symbols": "^1.0.0", 428 | "object-keys": "^1.0.11" 429 | }, 430 | "engines": { 431 | "node": ">= 0.4" 432 | } 433 | }, 434 | "node_modules/parse-json": { 435 | "version": "4.0.0", 436 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 437 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 438 | "dev": true, 439 | "dependencies": { 440 | "error-ex": "^1.3.1", 441 | "json-parse-better-errors": "^1.0.1" 442 | }, 443 | "engines": { 444 | "node": ">=4" 445 | } 446 | }, 447 | "node_modules/path-key": { 448 | "version": "2.0.1", 449 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 450 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 451 | "dev": true, 452 | "engines": { 453 | "node": ">=4" 454 | } 455 | }, 456 | "node_modules/path-parse": { 457 | "version": "1.0.7", 458 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 459 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 460 | "dev": true 461 | }, 462 | "node_modules/path-type": { 463 | "version": "3.0.0", 464 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", 465 | "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", 466 | "dev": true, 467 | "dependencies": { 468 | "pify": "^3.0.0" 469 | }, 470 | "engines": { 471 | "node": ">=4" 472 | } 473 | }, 474 | "node_modules/pidtree": { 475 | "version": "0.3.0", 476 | "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", 477 | "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", 478 | "dev": true, 479 | "bin": { 480 | "pidtree": "bin/pidtree.js" 481 | }, 482 | "engines": { 483 | "node": ">=0.10" 484 | } 485 | }, 486 | "node_modules/pify": { 487 | "version": "3.0.0", 488 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 489 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 490 | "dev": true, 491 | "engines": { 492 | "node": ">=4" 493 | } 494 | }, 495 | "node_modules/read-pkg": { 496 | "version": "3.0.0", 497 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", 498 | "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", 499 | "dev": true, 500 | "dependencies": { 501 | "load-json-file": "^4.0.0", 502 | "normalize-package-data": "^2.3.2", 503 | "path-type": "^3.0.0" 504 | }, 505 | "engines": { 506 | "node": ">=4" 507 | } 508 | }, 509 | "node_modules/resolve": { 510 | "version": "1.14.2", 511 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", 512 | "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", 513 | "dev": true, 514 | "dependencies": { 515 | "path-parse": "^1.0.6" 516 | }, 517 | "funding": { 518 | "url": "https://github.com/sponsors/ljharb" 519 | } 520 | }, 521 | "node_modules/semver": { 522 | "version": "5.7.1", 523 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 524 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 525 | "dev": true, 526 | "bin": { 527 | "semver": "bin/semver" 528 | } 529 | }, 530 | "node_modules/shebang-command": { 531 | "version": "1.2.0", 532 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 533 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 534 | "dev": true, 535 | "dependencies": { 536 | "shebang-regex": "^1.0.0" 537 | }, 538 | "engines": { 539 | "node": ">=0.10.0" 540 | } 541 | }, 542 | "node_modules/shebang-regex": { 543 | "version": "1.0.0", 544 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 545 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 546 | "dev": true, 547 | "engines": { 548 | "node": ">=0.10.0" 549 | } 550 | }, 551 | "node_modules/shell-quote": { 552 | "version": "1.7.2", 553 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", 554 | "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", 555 | "dev": true 556 | }, 557 | "node_modules/spdx-correct": { 558 | "version": "3.1.0", 559 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 560 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 561 | "dev": true, 562 | "dependencies": { 563 | "spdx-expression-parse": "^3.0.0", 564 | "spdx-license-ids": "^3.0.0" 565 | } 566 | }, 567 | "node_modules/spdx-exceptions": { 568 | "version": "2.2.0", 569 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 570 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 571 | "dev": true 572 | }, 573 | "node_modules/spdx-expression-parse": { 574 | "version": "3.0.0", 575 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 576 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 577 | "dev": true, 578 | "dependencies": { 579 | "spdx-exceptions": "^2.1.0", 580 | "spdx-license-ids": "^3.0.0" 581 | } 582 | }, 583 | "node_modules/spdx-license-ids": { 584 | "version": "3.0.5", 585 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", 586 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", 587 | "dev": true 588 | }, 589 | "node_modules/string.prototype.padend": { 590 | "version": "3.1.0", 591 | "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", 592 | "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", 593 | "dev": true, 594 | "dependencies": { 595 | "define-properties": "^1.1.3", 596 | "es-abstract": "^1.17.0-next.1" 597 | }, 598 | "engines": { 599 | "node": ">= 0.4" 600 | }, 601 | "funding": { 602 | "url": "https://github.com/sponsors/ljharb" 603 | } 604 | }, 605 | "node_modules/string.prototype.trimleft": { 606 | "version": "2.1.1", 607 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", 608 | "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", 609 | "dev": true, 610 | "dependencies": { 611 | "define-properties": "^1.1.3", 612 | "function-bind": "^1.1.1" 613 | }, 614 | "engines": { 615 | "node": ">= 0.4" 616 | }, 617 | "funding": { 618 | "url": "https://github.com/sponsors/ljharb" 619 | } 620 | }, 621 | "node_modules/string.prototype.trimright": { 622 | "version": "2.1.1", 623 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", 624 | "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", 625 | "dev": true, 626 | "dependencies": { 627 | "define-properties": "^1.1.3", 628 | "function-bind": "^1.1.1" 629 | }, 630 | "engines": { 631 | "node": ">= 0.4" 632 | }, 633 | "funding": { 634 | "url": "https://github.com/sponsors/ljharb" 635 | } 636 | }, 637 | "node_modules/strip-bom": { 638 | "version": "3.0.0", 639 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 640 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 641 | "dev": true, 642 | "engines": { 643 | "node": ">=4" 644 | } 645 | }, 646 | "node_modules/supports-color": { 647 | "version": "5.5.0", 648 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 649 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 650 | "dev": true, 651 | "dependencies": { 652 | "has-flag": "^3.0.0" 653 | }, 654 | "engines": { 655 | "node": ">=4" 656 | } 657 | }, 658 | "node_modules/validate-npm-package-license": { 659 | "version": "3.0.4", 660 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 661 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 662 | "dev": true, 663 | "dependencies": { 664 | "spdx-correct": "^3.0.0", 665 | "spdx-expression-parse": "^3.0.0" 666 | } 667 | }, 668 | "node_modules/which": { 669 | "version": "1.3.1", 670 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 671 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 672 | "dev": true, 673 | "dependencies": { 674 | "isexe": "^2.0.0" 675 | }, 676 | "bin": { 677 | "which": "bin/which" 678 | } 679 | } 680 | }, 681 | "dependencies": { 682 | "@types/geojson": { 683 | "version": "7946.0.8", 684 | "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", 685 | "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", 686 | "dev": true 687 | }, 688 | "@types/leaflet": { 689 | "version": "1.7.9", 690 | "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.9.tgz", 691 | "integrity": "sha512-H8vPgD49HKzqM41ArHGZM70g/tfhp8W+JcPxfnF+5H/Xvp+xiP+KQOUNWU8U89fqS1Jj3cpRY/+nbnaHFzwnFA==", 692 | "dev": true, 693 | "requires": { 694 | "@types/geojson": "*" 695 | } 696 | }, 697 | "ansi-styles": { 698 | "version": "3.2.1", 699 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 700 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 701 | "dev": true, 702 | "requires": { 703 | "color-convert": "^1.9.0" 704 | } 705 | }, 706 | "balanced-match": { 707 | "version": "1.0.0", 708 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 709 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 710 | "dev": true 711 | }, 712 | "brace-expansion": { 713 | "version": "1.1.11", 714 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 715 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 716 | "dev": true, 717 | "requires": { 718 | "balanced-match": "^1.0.0", 719 | "concat-map": "0.0.1" 720 | } 721 | }, 722 | "chalk": { 723 | "version": "2.4.2", 724 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 725 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 726 | "dev": true, 727 | "requires": { 728 | "ansi-styles": "^3.2.1", 729 | "escape-string-regexp": "^1.0.5", 730 | "supports-color": "^5.3.0" 731 | } 732 | }, 733 | "color-convert": { 734 | "version": "1.9.3", 735 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 736 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 737 | "dev": true, 738 | "requires": { 739 | "color-name": "1.1.3" 740 | } 741 | }, 742 | "color-name": { 743 | "version": "1.1.3", 744 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 745 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 746 | "dev": true 747 | }, 748 | "concat-map": { 749 | "version": "0.0.1", 750 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 751 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 752 | "dev": true 753 | }, 754 | "cross-spawn": { 755 | "version": "6.0.5", 756 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 757 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 758 | "dev": true, 759 | "requires": { 760 | "nice-try": "^1.0.4", 761 | "path-key": "^2.0.1", 762 | "semver": "^5.5.0", 763 | "shebang-command": "^1.2.0", 764 | "which": "^1.2.9" 765 | } 766 | }, 767 | "define-properties": { 768 | "version": "1.1.3", 769 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 770 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 771 | "dev": true, 772 | "requires": { 773 | "object-keys": "^1.0.12" 774 | } 775 | }, 776 | "error-ex": { 777 | "version": "1.3.2", 778 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 779 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 780 | "dev": true, 781 | "requires": { 782 | "is-arrayish": "^0.2.1" 783 | } 784 | }, 785 | "es-abstract": { 786 | "version": "1.17.0", 787 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0.tgz", 788 | "integrity": "sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==", 789 | "dev": true, 790 | "requires": { 791 | "es-to-primitive": "^1.2.1", 792 | "function-bind": "^1.1.1", 793 | "has": "^1.0.3", 794 | "has-symbols": "^1.0.1", 795 | "is-callable": "^1.1.5", 796 | "is-regex": "^1.0.5", 797 | "object-inspect": "^1.7.0", 798 | "object-keys": "^1.1.1", 799 | "object.assign": "^4.1.0", 800 | "string.prototype.trimleft": "^2.1.1", 801 | "string.prototype.trimright": "^2.1.1" 802 | } 803 | }, 804 | "es-to-primitive": { 805 | "version": "1.2.1", 806 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 807 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 808 | "dev": true, 809 | "requires": { 810 | "is-callable": "^1.1.4", 811 | "is-date-object": "^1.0.1", 812 | "is-symbol": "^1.0.2" 813 | } 814 | }, 815 | "escape-string-regexp": { 816 | "version": "1.0.5", 817 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 818 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 819 | "dev": true 820 | }, 821 | "function-bind": { 822 | "version": "1.1.1", 823 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 824 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 825 | "dev": true 826 | }, 827 | "graceful-fs": { 828 | "version": "4.2.3", 829 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 830 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", 831 | "dev": true 832 | }, 833 | "has": { 834 | "version": "1.0.3", 835 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 836 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 837 | "dev": true, 838 | "requires": { 839 | "function-bind": "^1.1.1" 840 | } 841 | }, 842 | "has-flag": { 843 | "version": "3.0.0", 844 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 845 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 846 | "dev": true 847 | }, 848 | "has-symbols": { 849 | "version": "1.0.1", 850 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 851 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 852 | "dev": true 853 | }, 854 | "hosted-git-info": { 855 | "version": "2.8.9", 856 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 857 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", 858 | "dev": true 859 | }, 860 | "is-arrayish": { 861 | "version": "0.2.1", 862 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 863 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 864 | "dev": true 865 | }, 866 | "is-callable": { 867 | "version": "1.1.5", 868 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", 869 | "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", 870 | "dev": true 871 | }, 872 | "is-date-object": { 873 | "version": "1.0.2", 874 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 875 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 876 | "dev": true 877 | }, 878 | "is-regex": { 879 | "version": "1.0.5", 880 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", 881 | "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", 882 | "dev": true, 883 | "requires": { 884 | "has": "^1.0.3" 885 | } 886 | }, 887 | "is-symbol": { 888 | "version": "1.0.3", 889 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 890 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 891 | "dev": true, 892 | "requires": { 893 | "has-symbols": "^1.0.1" 894 | } 895 | }, 896 | "isexe": { 897 | "version": "2.0.0", 898 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 899 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 900 | "dev": true 901 | }, 902 | "json-parse-better-errors": { 903 | "version": "1.0.2", 904 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 905 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 906 | "dev": true 907 | }, 908 | "leaflet": { 909 | "version": "1.7.1", 910 | "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", 911 | "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" 912 | }, 913 | "leaflet-geometryutil": { 914 | "version": "0.10.0", 915 | "resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.10.0.tgz", 916 | "integrity": "sha512-4OGu2OnpHLx+7QpOY6NatrAiSWWrP/Z3HOM9ZsmQ0JWIzAytFS6SLUnKHVjwpgRzPUpV/6w0b4Fh24pVIgqFHw==", 917 | "requires": { 918 | "leaflet": "^1.6.0" 919 | } 920 | }, 921 | "load-json-file": { 922 | "version": "4.0.0", 923 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 924 | "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", 925 | "dev": true, 926 | "requires": { 927 | "graceful-fs": "^4.1.2", 928 | "parse-json": "^4.0.0", 929 | "pify": "^3.0.0", 930 | "strip-bom": "^3.0.0" 931 | } 932 | }, 933 | "memorystream": { 934 | "version": "0.3.1", 935 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 936 | "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", 937 | "dev": true 938 | }, 939 | "minimatch": { 940 | "version": "3.0.4", 941 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 942 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 943 | "dev": true, 944 | "requires": { 945 | "brace-expansion": "^1.1.7" 946 | } 947 | }, 948 | "nice-try": { 949 | "version": "1.0.5", 950 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 951 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 952 | "dev": true 953 | }, 954 | "normalize-package-data": { 955 | "version": "2.5.0", 956 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 957 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 958 | "dev": true, 959 | "requires": { 960 | "hosted-git-info": "^2.1.4", 961 | "resolve": "^1.10.0", 962 | "semver": "2 || 3 || 4 || 5", 963 | "validate-npm-package-license": "^3.0.1" 964 | } 965 | }, 966 | "npm-run-all": { 967 | "version": "4.1.5", 968 | "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", 969 | "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", 970 | "dev": true, 971 | "requires": { 972 | "ansi-styles": "^3.2.1", 973 | "chalk": "^2.4.1", 974 | "cross-spawn": "^6.0.5", 975 | "memorystream": "^0.3.1", 976 | "minimatch": "^3.0.4", 977 | "pidtree": "^0.3.0", 978 | "read-pkg": "^3.0.0", 979 | "shell-quote": "^1.6.1", 980 | "string.prototype.padend": "^3.0.0" 981 | } 982 | }, 983 | "object-inspect": { 984 | "version": "1.7.0", 985 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", 986 | "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", 987 | "dev": true 988 | }, 989 | "object-keys": { 990 | "version": "1.1.1", 991 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 992 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 993 | "dev": true 994 | }, 995 | "object.assign": { 996 | "version": "4.1.0", 997 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 998 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 999 | "dev": true, 1000 | "requires": { 1001 | "define-properties": "^1.1.2", 1002 | "function-bind": "^1.1.1", 1003 | "has-symbols": "^1.0.0", 1004 | "object-keys": "^1.0.11" 1005 | } 1006 | }, 1007 | "parse-json": { 1008 | "version": "4.0.0", 1009 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 1010 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 1011 | "dev": true, 1012 | "requires": { 1013 | "error-ex": "^1.3.1", 1014 | "json-parse-better-errors": "^1.0.1" 1015 | } 1016 | }, 1017 | "path-key": { 1018 | "version": "2.0.1", 1019 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1020 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1021 | "dev": true 1022 | }, 1023 | "path-parse": { 1024 | "version": "1.0.7", 1025 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1026 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1027 | "dev": true 1028 | }, 1029 | "path-type": { 1030 | "version": "3.0.0", 1031 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", 1032 | "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", 1033 | "dev": true, 1034 | "requires": { 1035 | "pify": "^3.0.0" 1036 | } 1037 | }, 1038 | "pidtree": { 1039 | "version": "0.3.0", 1040 | "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", 1041 | "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", 1042 | "dev": true 1043 | }, 1044 | "pify": { 1045 | "version": "3.0.0", 1046 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 1047 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 1048 | "dev": true 1049 | }, 1050 | "read-pkg": { 1051 | "version": "3.0.0", 1052 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", 1053 | "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", 1054 | "dev": true, 1055 | "requires": { 1056 | "load-json-file": "^4.0.0", 1057 | "normalize-package-data": "^2.3.2", 1058 | "path-type": "^3.0.0" 1059 | } 1060 | }, 1061 | "resolve": { 1062 | "version": "1.14.2", 1063 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", 1064 | "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", 1065 | "dev": true, 1066 | "requires": { 1067 | "path-parse": "^1.0.6" 1068 | } 1069 | }, 1070 | "semver": { 1071 | "version": "5.7.1", 1072 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1073 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1074 | "dev": true 1075 | }, 1076 | "shebang-command": { 1077 | "version": "1.2.0", 1078 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1079 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1080 | "dev": true, 1081 | "requires": { 1082 | "shebang-regex": "^1.0.0" 1083 | } 1084 | }, 1085 | "shebang-regex": { 1086 | "version": "1.0.0", 1087 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1088 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1089 | "dev": true 1090 | }, 1091 | "shell-quote": { 1092 | "version": "1.7.2", 1093 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", 1094 | "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", 1095 | "dev": true 1096 | }, 1097 | "spdx-correct": { 1098 | "version": "3.1.0", 1099 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 1100 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 1101 | "dev": true, 1102 | "requires": { 1103 | "spdx-expression-parse": "^3.0.0", 1104 | "spdx-license-ids": "^3.0.0" 1105 | } 1106 | }, 1107 | "spdx-exceptions": { 1108 | "version": "2.2.0", 1109 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 1110 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 1111 | "dev": true 1112 | }, 1113 | "spdx-expression-parse": { 1114 | "version": "3.0.0", 1115 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 1116 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 1117 | "dev": true, 1118 | "requires": { 1119 | "spdx-exceptions": "^2.1.0", 1120 | "spdx-license-ids": "^3.0.0" 1121 | } 1122 | }, 1123 | "spdx-license-ids": { 1124 | "version": "3.0.5", 1125 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", 1126 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", 1127 | "dev": true 1128 | }, 1129 | "string.prototype.padend": { 1130 | "version": "3.1.0", 1131 | "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", 1132 | "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", 1133 | "dev": true, 1134 | "requires": { 1135 | "define-properties": "^1.1.3", 1136 | "es-abstract": "^1.17.0-next.1" 1137 | } 1138 | }, 1139 | "string.prototype.trimleft": { 1140 | "version": "2.1.1", 1141 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", 1142 | "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", 1143 | "dev": true, 1144 | "requires": { 1145 | "define-properties": "^1.1.3", 1146 | "function-bind": "^1.1.1" 1147 | } 1148 | }, 1149 | "string.prototype.trimright": { 1150 | "version": "2.1.1", 1151 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", 1152 | "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", 1153 | "dev": true, 1154 | "requires": { 1155 | "define-properties": "^1.1.3", 1156 | "function-bind": "^1.1.1" 1157 | } 1158 | }, 1159 | "strip-bom": { 1160 | "version": "3.0.0", 1161 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1162 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1163 | "dev": true 1164 | }, 1165 | "supports-color": { 1166 | "version": "5.5.0", 1167 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1168 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1169 | "dev": true, 1170 | "requires": { 1171 | "has-flag": "^3.0.0" 1172 | } 1173 | }, 1174 | "validate-npm-package-license": { 1175 | "version": "3.0.4", 1176 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1177 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1178 | "dev": true, 1179 | "requires": { 1180 | "spdx-correct": "^3.0.0", 1181 | "spdx-expression-parse": "^3.0.0" 1182 | } 1183 | }, 1184 | "which": { 1185 | "version": "1.3.1", 1186 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1187 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1188 | "dev": true, 1189 | "requires": { 1190 | "isexe": "^2.0.0" 1191 | } 1192 | } 1193 | } 1194 | } 1195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-arrowheads", 3 | "version": "1.4.0", 4 | "description": "Add arrow heads to leaflet polylines", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "checkoutput": "npm pack && tar -xvzf *.tgz && rm -rf package *.tgz", 8 | "use:npmReadme": "mv 'README.md' 'git.README.md' && mv 'npm.README.md' 'README.md'", 9 | "use:gitReadme": "mv 'README.md' 'npm.README.md' && mv 'git.README.md' 'README.md'", 10 | "prepublishOnly": "npm run use:npmReadme", 11 | "postpublish": "npm run use:gitReadme" 12 | }, 13 | "keywords": [ 14 | "leaflet", 15 | "vectorhat", 16 | "vector", 17 | "hat", 18 | "arrowhead", 19 | "arrow" 20 | ], 21 | "author": "Seth Lutske", 22 | "license": "MIT", 23 | "dependencies": { 24 | "leaflet": "^1.7.1", 25 | "leaflet-geometryutil": "^0.10.0" 26 | }, 27 | "devDependencies": { 28 | "@types/leaflet": "^1.7.9", 29 | "npm-run-all": "^4.1.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as L from 'leaflet'; 2 | 3 | interface SingleArrowheadOptions { 4 | /** 5 | * The angle of opening of the arrowheads in degrees. Defaults to 60. 6 | */ 7 | yawn?: number; 8 | /** 9 | * The size of the arrowheads, give as a string specifying pixels, %, or meters. 10 | * Defaults to '15%' 11 | */ 12 | size?: `${number}px` | `${number}%` | `${number}m`; 13 | } 14 | 15 | interface ArrowheadOptions extends L.PolylineOptions, SingleArrowheadOptions { 16 | /** 17 | * The number and spacing of arrowheads to draw along the polyline. 18 | * Defaults to 'allvertices' 19 | */ 20 | frequency?: number | `${number}px` | `${number}m` | 'allvertices' | 'endonly'; 21 | /** 22 | * If the size of the arrowheads, when given in percent, should be a percentage proportional 23 | * to the total length of the polyline, rather than the average of all the segments. 24 | * Defaults to false 25 | */ 26 | proportionalToTotal?: boolean; 27 | /** 28 | * The offsets from start of end of where to begin/end drawing arrowheads. 29 | * Deafult to undefined (no offets) 30 | */ 31 | offsets?: { 32 | start?: `${number}px` | `${number}m`; 33 | end?: `${number}px` | `${number}m`; 34 | }; 35 | /** 36 | * Callback function to customize arrowheads on a 1-by-1 basis 37 | * Defaults to undefined 38 | */ 39 | perArrowheadOptions?: ( 40 | i: number 41 | ) => L.PolylineOptions & SingleArrowheadOptions; 42 | } 43 | 44 | declare module 'leaflet' { 45 | export interface Polyline { 46 | /** 47 | * Adds arrowheads to an L.polyline. See documentation at https://github.com/slutske22/leaflet-arrowheads 48 | * @param {object} options The options for the arrowhead. See documentation for details 49 | * @returns The L.polyline instance that they arrowheads are attached to 50 | */ 51 | arrowheads: (options: ArrowheadOptions) => L.Polyline; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'leaflet-geometryutil' 2 | import './leaflet-arrowheads.js' -------------------------------------------------------------------------------- /src/leaflet-arrowheads.js: -------------------------------------------------------------------------------- 1 | function modulus(i, n) { 2 | return ((i % n) + n) % n; 3 | } 4 | 5 | function definedProps(obj) { 6 | return Object.fromEntries( 7 | Object.entries(obj).filter(([k, v]) => v !== undefined) 8 | ); 9 | } 10 | 11 | /** 12 | * Whether or not a string is in the format 'm' 13 | * @param {string} value 14 | * @returns Boolean 15 | */ 16 | function isInMeters(value) { 17 | return ( 18 | value 19 | .toString() 20 | .trim() 21 | .slice(value.toString().length - 1, value.toString().length) === 'm' 22 | ); 23 | } 24 | 25 | /** 26 | * Whether or not a string is in the format '%' 27 | * @param {string} value 28 | * @returns Boolean 29 | */ 30 | function isInPercent(value) { 31 | return ( 32 | value 33 | .toString() 34 | .trim() 35 | .slice(value.toString().length - 1, value.toString().length) === '%' 36 | ); 37 | } 38 | 39 | /** 40 | * Whether or not a string is in the format 'px' 41 | * @param {string} value 42 | * @returns Boolean 43 | */ 44 | function isInPixels(value) { 45 | return ( 46 | value 47 | .toString() 48 | .trim() 49 | .slice(value.toString().length - 2, value.toString().length) === 'px' 50 | ); 51 | } 52 | 53 | function pixelsToMeters(pixels, map) { 54 | let refPoint1 = map.getCenter(); 55 | let xy1 = map.latLngToLayerPoint(refPoint1); 56 | let xy2 = { 57 | x: xy1.x + Number(pixels), 58 | y: xy1.y, 59 | }; 60 | let refPoint2 = map.layerPointToLatLng(xy2); 61 | let derivedMeters = map.distance(refPoint1, refPoint2); 62 | return derivedMeters; 63 | } 64 | 65 | L.Polyline.include({ 66 | /** 67 | * Adds arrowheads to an L.polyline 68 | * @param {object} options The options for the arrowhead. See documentation for details 69 | * @returns The L.polyline instance that they arrowheads are attached to 70 | */ 71 | arrowheads: function (options = {}) { 72 | // Merge user input options with default options: 73 | const defaults = { 74 | yawn: 60, 75 | size: '15%', 76 | frequency: 'allvertices', 77 | proportionalToTotal: false, 78 | }; 79 | 80 | this.options.noClip = true; 81 | 82 | let actualOptions = Object.assign({}, defaults, options); 83 | this._arrowheadOptions = actualOptions; 84 | 85 | this._hatsApplied = true; 86 | return this; 87 | }, 88 | 89 | buildVectorHats: function (options) { 90 | // Reset variables from previous this._update() 91 | if (this._arrowheads) { 92 | this._arrowheads.remove(); 93 | } 94 | 95 | if (this._ghosts) { 96 | this._ghosts.remove(); 97 | } 98 | 99 | // -------------------------------------------------------- // 100 | // ------------ FILTER THE OPTIONS ----------------------- // 101 | /* 102 | * The next 3 lines folds the options of the parent polyline into the default options for all polylines 103 | * The options for the arrowhead are then folded in as well 104 | * All options defined in parent polyline will be inherited by the arrowhead, unless otherwise specified in the arrowhead(options) call 105 | */ 106 | 107 | let defaultOptionsOfParent = Object.getPrototypeOf( 108 | Object.getPrototypeOf(this.options) 109 | ); 110 | 111 | // merge default options of parent polyline (this.options's prototype's prototype) with options passed to parent polyline (this.options). 112 | let parentOptions = Object.assign({}, defaultOptionsOfParent, this.options); 113 | 114 | // now merge in the options the user has put in the arrowhead call 115 | let hatOptions = Object.assign({}, parentOptions, options); 116 | 117 | // ...with a few exceptions: 118 | hatOptions.smoothFactor = 1; 119 | hatOptions.fillOpacity = 1; 120 | hatOptions.fill = options.fill ? true : false; 121 | hatOptions.interactive = false; 122 | 123 | // ------------ FILTER THE OPTIONS END -------------------- // 124 | // --------------------------------------------------------- // 125 | 126 | // --------------------------------------------------------- // 127 | // ------ LOOP THROUGH EACH POLYLINE SEGMENT --------------- // 128 | // ------ TO CALCULATE HAT SIZES AND CAPTURE IN ARRAY ------ // 129 | 130 | let size = options.size.toString(); // stringify if its a number 131 | let allhats = []; // empty array to receive hat polylines 132 | const { frequency, offsets } = options; 133 | 134 | if (offsets?.start || offsets?.end) { 135 | this._buildGhosts({ start: offsets.start, end: offsets.end }); 136 | } 137 | 138 | const lineToTrace = this._ghosts || this; 139 | 140 | lineToTrace._parts.forEach((peice, index) => { 141 | // Immutable variables for each peice 142 | const latlngs = peice.map((point) => this._map.layerPointToLatLng(point)); 143 | 144 | const totalLength = (() => { 145 | let total = 0; 146 | for (var i = 0; i < peice.length - 1; i++) { 147 | total += this._map.distance(latlngs[i], latlngs[i + 1]); 148 | } 149 | return total; 150 | })(); 151 | 152 | // TBD by options if tree below 153 | let derivedLatLngs; 154 | let derivedBearings; 155 | let spacing; 156 | let noOfPoints; 157 | 158 | // Determining latlng and bearing arrays based on frequency choice: 159 | if (!isNaN(frequency)) { 160 | spacing = 1 / frequency; 161 | noOfPoints = frequency; 162 | } else if (isInPercent(frequency)) { 163 | console.error( 164 | 'Error: arrowhead frequency option cannot be given in percent. Try another unit.' 165 | ); 166 | } else if (isInMeters(frequency)) { 167 | spacing = frequency.slice(0, frequency.length - 1) / totalLength; 168 | noOfPoints = 1 / spacing; 169 | // round things out for more even spacing: 170 | noOfPoints = Math.floor(noOfPoints); 171 | spacing = 1 / noOfPoints; 172 | } else if (isInPixels(frequency)) { 173 | spacing = (() => { 174 | let chosenFrequency = frequency.slice(0, frequency.length - 2); 175 | let derivedMeters = pixelsToMeters(chosenFrequency, this._map); 176 | return derivedMeters / totalLength; 177 | })(); 178 | 179 | noOfPoints = 1 / spacing; 180 | 181 | // round things out for more even spacing: 182 | noOfPoints = Math.floor(noOfPoints); 183 | spacing = 1 / noOfPoints; 184 | } 185 | 186 | if (options.frequency === 'allvertices') { 187 | derivedBearings = (() => { 188 | let bearings = []; 189 | for (var i = 1; i < latlngs.length; i++) { 190 | let bearing = 191 | L.GeometryUtil.angle( 192 | this._map, 193 | latlngs[modulus(i - 1, latlngs.length)], 194 | latlngs[i] 195 | ) + 180; 196 | bearings.push(bearing); 197 | } 198 | return bearings; 199 | })(); 200 | 201 | derivedLatLngs = latlngs; 202 | derivedLatLngs.shift(); 203 | } else if (options.frequency === 'endonly' && latlngs.length >= 2) { 204 | derivedLatLngs = [latlngs[latlngs.length - 1]]; 205 | 206 | derivedBearings = [ 207 | L.GeometryUtil.angle( 208 | this._map, 209 | latlngs[latlngs.length - 2], 210 | latlngs[latlngs.length - 1] 211 | ) + 180, 212 | ]; 213 | } else { 214 | derivedLatLngs = []; 215 | let interpolatedPoints = []; 216 | for (var i = 0; i < noOfPoints; i++) { 217 | let interpolatedPoint = L.GeometryUtil.interpolateOnLine( 218 | this._map, 219 | latlngs, 220 | spacing * (i + 1) 221 | ); 222 | 223 | if (interpolatedPoint) { 224 | interpolatedPoints.push(interpolatedPoint); 225 | derivedLatLngs.push(interpolatedPoint.latLng); 226 | } 227 | } 228 | 229 | derivedBearings = (() => { 230 | let bearings = []; 231 | 232 | for (var i = 0; i < interpolatedPoints.length; i++) { 233 | let bearing = L.GeometryUtil.angle( 234 | this._map, 235 | latlngs[interpolatedPoints[i].predecessor + 1], 236 | latlngs[interpolatedPoints[i].predecessor] 237 | ); 238 | bearings.push(bearing); 239 | } 240 | return bearings; 241 | })(); 242 | } 243 | 244 | let hats = []; 245 | 246 | // Function to build hats based on index and a given hatsize in meters 247 | const pushHats = (size, localHatOptions = {}) => { 248 | let yawn = localHatOptions.yawn ?? options.yawn; 249 | 250 | let leftWingPoint = L.GeometryUtil.destination( 251 | derivedLatLngs[i], 252 | derivedBearings[i] - yawn / 2, 253 | size 254 | ); 255 | 256 | let rightWingPoint = L.GeometryUtil.destination( 257 | derivedLatLngs[i], 258 | derivedBearings[i] + yawn / 2, 259 | size 260 | ); 261 | 262 | let hatPoints = [ 263 | [leftWingPoint.lat, leftWingPoint.lng], 264 | [derivedLatLngs[i].lat, derivedLatLngs[i].lng], 265 | [rightWingPoint.lat, rightWingPoint.lng], 266 | ]; 267 | 268 | let hat = options.fill 269 | ? L.polygon(hatPoints, { ...hatOptions, ...localHatOptions }) 270 | : L.polyline(hatPoints, { ...hatOptions, ...localHatOptions }); 271 | 272 | hats.push(hat); 273 | }; // pushHats() 274 | 275 | // Function to build hats based on pixel input 276 | const pushHatsFromPixels = (size, localHatOptions = {}) => { 277 | let sizePixels = size.slice(0, size.length - 2); 278 | let yawn = localHatOptions.yawn ?? options.yawn; 279 | 280 | let derivedXY = this._map.latLngToLayerPoint(derivedLatLngs[i]); 281 | 282 | let bearing = derivedBearings[i]; 283 | 284 | let thetaLeft = (180 - bearing - yawn / 2) * (Math.PI / 180), 285 | thetaRight = (180 - bearing + yawn / 2) * (Math.PI / 180); 286 | 287 | let dxLeft = sizePixels * Math.sin(thetaLeft), 288 | dyLeft = sizePixels * Math.cos(thetaLeft), 289 | dxRight = sizePixels * Math.sin(thetaRight), 290 | dyRight = sizePixels * Math.cos(thetaRight); 291 | 292 | let leftWingXY = { 293 | x: derivedXY.x + dxLeft, 294 | y: derivedXY.y + dyLeft, 295 | }; 296 | let rightWingXY = { 297 | x: derivedXY.x + dxRight, 298 | y: derivedXY.y + dyRight, 299 | }; 300 | 301 | let leftWingPoint = this._map.layerPointToLatLng(leftWingXY), 302 | rightWingPoint = this._map.layerPointToLatLng(rightWingXY); 303 | 304 | let hatPoints = [ 305 | [leftWingPoint.lat, leftWingPoint.lng], 306 | [derivedLatLngs[i].lat, derivedLatLngs[i].lng], 307 | [rightWingPoint.lat, rightWingPoint.lng], 308 | ]; 309 | 310 | let hat = options.fill 311 | ? L.polygon(hatPoints, { ...hatOptions, ...localHatOptions }) 312 | : L.polyline(hatPoints, { ...hatOptions, ...localHatOptions }); 313 | 314 | hats.push(hat); 315 | }; // pushHatsFromPixels() 316 | 317 | // ------- LOOP THROUGH POINTS IN EACH SEGMENT ---------- // 318 | for (var i = 0; i < derivedLatLngs.length; i++) { 319 | let { perArrowheadOptions, ...globalOptions } = options; 320 | 321 | perArrowheadOptions = perArrowheadOptions ? perArrowheadOptions(i) : {}; 322 | perArrowheadOptions = Object.assign( 323 | globalOptions, 324 | definedProps(perArrowheadOptions) 325 | ); 326 | 327 | size = perArrowheadOptions.size ?? size; 328 | 329 | // ---- If size is chosen in meters ------------------------- 330 | if (isInMeters(size)) { 331 | let hatSize = size.slice(0, size.length - 1); 332 | pushHats(hatSize, perArrowheadOptions); 333 | 334 | // ---- If size is chosen in percent ------------------------ 335 | } else if (isInPercent(size)) { 336 | let sizePercent = size.slice(0, size.length - 1); 337 | let hatSize = (() => { 338 | if ( 339 | options.frequency === 'endonly' && 340 | options.proportionalToTotal 341 | ) { 342 | return (totalLength * sizePercent) / 100; 343 | } else { 344 | let averageDistance = totalLength / (peice.length - 1); 345 | return (averageDistance * sizePercent) / 100; 346 | } 347 | })(); // hatsize calculation 348 | 349 | pushHats(hatSize, perArrowheadOptions); 350 | 351 | // ---- If size is chosen in pixels -------------------------- 352 | } else if (isInPixels(size)) { 353 | pushHatsFromPixels(options.size, perArrowheadOptions); 354 | 355 | // ---- If size unit is not given ----------------------------- 356 | } else { 357 | console.error( 358 | 'Error: Arrowhead size unit not defined. Check your arrowhead options.' 359 | ); 360 | } // if else block for Size 361 | } // for loop for each point witin a peice 362 | 363 | allhats.push(...hats); 364 | }); // forEach peice 365 | 366 | // --------- LOOP THROUGH EACH POLYLINE END ---------------- // 367 | // --------------------------------------------------------- // 368 | 369 | let arrowheads = L.layerGroup(allhats); 370 | this._arrowheads = arrowheads; 371 | 372 | return this; 373 | }, 374 | 375 | getArrowheads: function () { 376 | if (this._arrowheads) { 377 | return this._arrowheads; 378 | } else { 379 | return console.error( 380 | `Error: You tried to call '.getArrowheads() on a shape that does not have a arrowhead. Use '.arrowheads()' to add a arrowheads before trying to call '.getArrowheads()'` 381 | ); 382 | } 383 | }, 384 | 385 | /** 386 | * Builds ghost polylines that are clipped versions of the polylines based on the offsets 387 | * If offsets are used, arrowheads are drawn from 'this._ghosts' rather than 'this' 388 | */ 389 | _buildGhosts: function ({ start, end }) { 390 | if (start || end) { 391 | let latlngs = this.getLatLngs(); 392 | 393 | latlngs = Array.isArray(latlngs[0]) ? latlngs : [latlngs]; 394 | 395 | const newLatLngs = latlngs.map((segment) => { 396 | // Get total distance of original latlngs 397 | const totalLength = (() => { 398 | let total = 0; 399 | for (var i = 0; i < segment.length - 1; i++) { 400 | total += this._map.distance(segment[i], segment[i + 1]); 401 | } 402 | return total; 403 | })(); 404 | 405 | // Modify latlngs to end at interpolated point 406 | if (start) { 407 | let endOffsetInMeters = (() => { 408 | if (isInMeters(start)) { 409 | return Number(start.slice(0, start.length - 1)); 410 | } else if (isInPixels(start)) { 411 | let pixels = Number(start.slice(0, start.length - 2)); 412 | return pixelsToMeters(pixels, this._map); 413 | } 414 | })(); 415 | 416 | let newStart = L.GeometryUtil.interpolateOnLine( 417 | this._map, 418 | segment, 419 | endOffsetInMeters / totalLength 420 | ); 421 | 422 | segment = segment.slice( 423 | newStart.predecessor === -1 ? 1 : newStart.predecessor + 1, 424 | segment.length 425 | ); 426 | segment.unshift(newStart.latLng); 427 | } 428 | 429 | if (end) { 430 | let endOffsetInMeters = (() => { 431 | if (isInMeters(end)) { 432 | return Number(end.slice(0, end.length - 1)); 433 | } else if (isInPixels(end)) { 434 | let pixels = Number(end.slice(0, end.length - 2)); 435 | return pixelsToMeters(pixels, this._map); 436 | } 437 | })(); 438 | 439 | let newEnd = L.GeometryUtil.interpolateOnLine( 440 | this._map, 441 | segment, 442 | (totalLength - endOffsetInMeters) / totalLength 443 | ); 444 | 445 | segment = segment.slice(0, newEnd.predecessor + 1); 446 | segment.push(newEnd.latLng); 447 | } 448 | 449 | return segment; 450 | }); 451 | 452 | this._ghosts = L.polyline(newLatLngs, { 453 | ...this.options, 454 | color: 'rgba(0,0,0,0)', 455 | stroke: 0, 456 | smoothFactor: 0, 457 | interactive: false, 458 | }); 459 | this._ghosts.addTo(this._map); 460 | } 461 | }, 462 | 463 | deleteArrowheads: function () { 464 | if (this._arrowheads) { 465 | this._arrowheads.remove(); 466 | delete this._arrowheads; 467 | delete this._arrowheadOptions; 468 | this._hatsApplied = false; 469 | } 470 | if (this._ghosts) { 471 | this._ghosts.remove(); 472 | } 473 | }, 474 | 475 | _update: function () { 476 | if (!this._map) { 477 | return; 478 | } 479 | 480 | this._clipPoints(); 481 | this._simplifyPoints(); 482 | this._updatePath(); 483 | 484 | if (this._hatsApplied) { 485 | this.buildVectorHats(this._arrowheadOptions); 486 | this._map.addLayer(this._arrowheads); 487 | } 488 | }, 489 | 490 | remove: function () { 491 | if (this._arrowheads) { 492 | this._arrowheads.remove(); 493 | } 494 | if (this._ghosts) { 495 | this._ghosts.remove(); 496 | } 497 | return this.removeFrom(this._map || this._mapToAdd); 498 | }, 499 | }); 500 | 501 | L.LayerGroup.include({ 502 | removeLayer: function (layer) { 503 | var id = layer in this._layers ? layer : this.getLayerId(layer); 504 | 505 | if (this._map && this._layers[id]) { 506 | if (this._layers[id]._arrowheads) { 507 | this._layers[id]._arrowheads.remove(); 508 | } 509 | this._map.removeLayer(this._layers[id]); 510 | } 511 | 512 | delete this._layers[id]; 513 | 514 | return this; 515 | }, 516 | 517 | onRemove: function (map, layer) { 518 | for (var layer in this._layers) { 519 | if (this._layers[layer]) { 520 | this._layers[layer].remove(); 521 | } 522 | } 523 | 524 | this.eachLayer(map.removeLayer, map); 525 | }, 526 | }); 527 | 528 | L.Map.include({ 529 | removeLayer: function (layer) { 530 | var id = L.Util.stamp(layer); 531 | 532 | if (layer._arrowheads) { 533 | layer._arrowheads.remove(); 534 | } 535 | if (layer._ghosts) { 536 | layer._ghosts.remove(); 537 | } 538 | 539 | if (!this._layers[id]) { 540 | return this; 541 | } 542 | 543 | if (this._loaded) { 544 | layer.onRemove(this); 545 | } 546 | 547 | if (layer.getAttribution && this.attributionControl) { 548 | this.attributionControl.removeAttribution(layer.getAttribution()); 549 | } 550 | 551 | delete this._layers[id]; 552 | 553 | if (this._loaded) { 554 | this.fire('layerremove', { layer: layer }); 555 | layer.fire('remove'); 556 | } 557 | 558 | layer._map = layer._mapToAdd = null; 559 | 560 | return this; 561 | }, 562 | }); 563 | 564 | L.GeoJSON.include({ 565 | geometryToLayer: function (geojson, options) { 566 | var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 567 | coords = geometry ? geometry.coordinates : null, 568 | layers = [], 569 | pointToLayer = options && options.pointToLayer, 570 | _coordsToLatLng = 571 | (options && options.coordsToLatLng) || L.GeoJSON.coordsToLatLng, 572 | latlng, 573 | latlngs, 574 | i, 575 | len; 576 | 577 | if (!coords && !geometry) { 578 | return null; 579 | } 580 | 581 | switch (geometry.type) { 582 | case 'Point': 583 | latlng = _coordsToLatLng(coords); 584 | return this._pointToLayer(pointToLayer, geojson, latlng, options); 585 | 586 | case 'MultiPoint': 587 | for (i = 0, len = coords.length; i < len; i++) { 588 | latlng = _coordsToLatLng(coords[i]); 589 | layers.push( 590 | this._pointToLayer(pointToLayer, geojson, latlng, options) 591 | ); 592 | } 593 | return new L.FeatureGroup(layers); 594 | 595 | case 'LineString': 596 | case 'MultiLineString': 597 | latlngs = L.GeoJSON.coordsToLatLngs( 598 | coords, 599 | geometry.type === 'LineString' ? 0 : 1, 600 | _coordsToLatLng 601 | ); 602 | var polyline = new L.Polyline(latlngs, options); 603 | if (options.arrowheads) { 604 | polyline.arrowheads(options.arrowheads); 605 | } 606 | return polyline; 607 | 608 | case 'Polygon': 609 | case 'MultiPolygon': 610 | latlngs = L.GeoJSON.coordsToLatLngs( 611 | coords, 612 | geometry.type === 'Polygon' ? 1 : 2, 613 | _coordsToLatLng 614 | ); 615 | return new L.Polygon(latlngs, options); 616 | 617 | case 'GeometryCollection': 618 | for (i = 0, len = geometry.geometries.length; i < len; i++) { 619 | var layer = this.geometryToLayer( 620 | { 621 | geometry: geometry.geometries[i], 622 | type: 'Feature', 623 | properties: geojson.properties, 624 | }, 625 | options 626 | ); 627 | 628 | if (layer) { 629 | layers.push(layer); 630 | } 631 | } 632 | return new L.FeatureGroup(layers); 633 | 634 | default: 635 | throw new Error('Invalid GeoJSON object.'); 636 | } 637 | }, 638 | 639 | addData: function (geojson) { 640 | var features = L.Util.isArray(geojson) ? geojson : geojson.features, 641 | i, 642 | len, 643 | feature; 644 | 645 | if (features) { 646 | for (i = 0, len = features.length; i < len; i++) { 647 | // only add this if geometry or geometries are set and not null 648 | feature = features[i]; 649 | if ( 650 | feature.geometries || 651 | feature.geometry || 652 | feature.features || 653 | feature.coordinates 654 | ) { 655 | this.addData(feature); 656 | } 657 | } 658 | return this; 659 | } 660 | 661 | var options = this.options; 662 | 663 | if (options.filter && !options.filter(geojson)) { 664 | return this; 665 | } 666 | 667 | var layer = this.geometryToLayer(geojson, options); 668 | if (!layer) { 669 | return this; 670 | } 671 | layer.feature = L.GeoJSON.asFeature(geojson); 672 | 673 | layer.defaultOptions = layer.options; 674 | this.resetStyle(layer); 675 | 676 | if (options.onEachFeature) { 677 | options.onEachFeature(geojson, layer); 678 | } 679 | 680 | return this.addLayer(layer); 681 | }, 682 | 683 | _pointToLayer: function (pointToLayerFn, geojson, latlng, options) { 684 | return pointToLayerFn 685 | ? pointToLayerFn(geojson, latlng) 686 | : new L.Marker( 687 | latlng, 688 | options && options.markersInheritOptions && options 689 | ); 690 | }, 691 | }); 692 | --------------------------------------------------------------------------------