npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

leaflet-course

v0.9.3

Published

Leaflet Control to help trace a route on earth, provides distance & bearing on great circle path.

Downloads

6

Readme

Leaflet.Control.Course

This plugin is heavily inspired by Leaflet.PolylineMeasure (even the README), I rewrote the plugin in a more 'leaflet' way using, markers, handler, icons, and so on in order to be more flexible.

  • Leaflet Plugin to trace routes on earth or sea.
  • Measuring distance, heading and bearing.
  • Lines are drawn as realistic arcs. Bearings and distances are calculated considering Great-circle distance which is the shortest path between 2 points on Earth.
  • Arrows indicating the real midways of the line's great-circle distances, not their optical middle which is different due to projection, especially in high latitudes.
  • To finish a route just click onto the last point, or press "ESC" key.
  • Moving of route's points afterwards is possible by dragging them.
  • Resume a route after it has been finished, by clicking onto the last point.
  • Insert points, by clicking onto an arrow.
  • Delete points, by right clicking onto a point.
  • Work on touchscreen too. rightclick become long press

Demo

  • Please take a look at the Demo

Usage

Add 1 line of code within your HTML-file to load the .js files of the plugin:

  <script src="src/js/leaflet-control-course.js"></script>

Add 1 line of code within your Javascript-file to add the plugin's control into your Leaflet map.

  L.control.course(<options ?>).addTo(map);

Alternatively you can install the plugin with npm : npm install leaflet-course

Options & defaults

The default options are the following.

  {
    position: 'topright',
    cursor: 'crosshair',
    tools: {
      route: {
        icon: '☡',
        title: 'Start a route',
        handler: L.Handler.RouteBase, // Should not be changed unless you extended the plugin
        waypoint: {
          icon: L.divIcon({ className: 'leaflet-control-course-icon', html: "<span class='leaflet-control-course-point'></span>", iconAnchor: [20, 18], iconSize: [40, 40] }),
          popup: p => `<pre>${JSON.stringify(L.Control.Course.extract(p.export()), null, 2)}</pre>`,
          tooltip: p => `<pre>${JSON.stringify(L.Control.Course.extract(p.export()), null, 2)}</pre>`,
        },
        midpoint: {
          icon: L.divIcon({ className: 'leaflet-control-course-icon', html: "<div class='leaflet-control-course-arrow'></div>", iconAnchor: [20, 18], iconSize: [40, 40] }),
          tooltip: 'Click to insert a waypoints here',
          opacity: 0.5
        },
        trace: { weight: 5, opacity: 0.5, color: 'black' }, // See Polyline for supported options
        pointer: { dashArray: '8' }, // See Polyline for supported options
      },
      bearing: {
        icon: '∡',
        title: 'Radio Navigation',
        handler: L.Handler.BearingBase, // Should not be changed unless you extended the plugin
        marker: {
          icon: L.divIcon({ className: 'leaflet-control-course-icon', html: "<span class='leaflet-control-course-losange'></span>", iconAnchor: [20, 18], iconSize: [40, 40] }),
          tooltip: p => `<pre>${JSON.stringify(L.Control.Course.extract(p.export()), null, 2)}</pre>`,
        },
        trace: { dashArray: '5,5,1,5', opacity: 0.3, color: 'grey', }, // See Polyline for supported options
        pointer: { dashArray: '8' } // See Polyline for supported options
      },
      track: {
        icon: '✈',// ✇
        title: 'Track Position',
        handler: L.Handler.TrackBase, // Should not be changed unless you extended the plugin
        marker: {
          icon: L.divIcon({ className: 'leaflet-control-course-icon', html: "<div class='leaflet-control-course-airplane'></div>", iconAnchor: [20, 18], iconSize: [40, 40] }),
          tooltip: p => `<pre>${JSON.stringify(L.Control.Course.extract(p.export()), null, 2)}</pre>`,
        },
        circle: {},
        trace: { weight: 5, opacity: 0.5, color: 'black' }, // See Polyline for supported options
        locate: { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, setView: true } // See map.locate() for supported options.
      },
      clear: {
        icon: '✗',// ⥇
        title: 'Clear routes',
        handler: L.Handler.ClearBase // Should not be changed unless you extended the plugin
      }
    }
  }

You can fine tune the control by setting different value, or undefined if you want to disable a specific feature.

eg: this one will disable the bearing measurement and track tools. It also will remove the popup on waypoints.

  {
    tools: {
      route: {
        waypoint: {
          popup: undefined,
        }
      },
      bearing: undefined,
      track: undefined,
    }
  }

You can also create a new tool prety easily. see the [extend section](##Extend the plugin with new tool) below, or ask in the issues tracker.

Styling

Traces and lines are styled in options with corresponding Leaflet object.

Markers icons are styled with embedded CSS. You can see it in source.

You can create new classes based on the current, just be sure to set the orientation of tracker or arrow icons.

Data structure

We use subclasses of leaflet objects.

<Control.Course>

Extends Control, will load the tools set in options. will handle display of icons, and handle the state of each tool. Got an export() method to to get current route or track drawn by the plugin.

export()

<LayerGroup.Route>

Represent a whole route, with points, lines and decorations, extends LayerGroup.

  <LayerGroup.Route>.waypoints // is a `LayerGroup` of Markers representing ... waypoints.
  <LayerGroup.Route>.trace // is a [`Leaflet.Geodesic`](https://github.com/henrythasler/Leaflet.Geodesic) hosting the Polyline
  <LayerGroup.Route>.midpoints // is a `LayerGroup` of Markers representing midpoint of each segment. They are generated each time the line is redrawn (on waypoint addition, insertion, removing).

  <LayerGroup.Route>.export() // return a representation of the route.

<Marker.Waypoint>

Waypoints are decorated with input bearing, output bearing, distance from last point and distance from beginning. they are chained to make a route. You can add more attributes (like, name, altitude, ...) with a hook on the course:waypoint:add events.

  <Marker.Waypoint>.data // {
  //   "in": 115.01,
  //   "out": 255.72,
  //   "distance": 2515.8,
  //   "totalDistance": 2515.8,
  // }
  <Marker.Waypoint>.previous // previous Waypoint in route or null
  <Marker.Waypoint>.next // next Waypoint in route or null
  <Marker.Waypoint>.fellow.bearings // array of Bearings associated to this Waypoint.

  <Marker.Waypoint>.export() // return a representation of the Waypoint.

to document : setSiblings(), toLast(), toFirst(), registerBearing(),

<Marker.Bearing>

Bearing are Markers decorated with input bearing, output bearing, distance. You can add more attributes (like, name, altitude, ...) with a hook on the course:waypoint:add events.

  <Marker.Bearing>.data // {
  //   "QDR": 115.01,
  //   "QDM": 255.72,
  //   "distance": 2515.8,
  // }
  <Marker.Bearing>.previous // previous Waypoint in route or null
  <Marker.Bearing>.next // next Waypoint in route or null
  <Marker.Bearing>.fellow.origin // Waypoint associated to this Bearing.

  <Marker.Bearing>.export() // return a representation of the Bearing.

<Marker.Trackpoint>

Importing and exporting routes & tracks

export()

import()

Events

It fire some events during the tracing in order to allow more interactivity with the app. Subscribe to events with :

map.on(
  'course:route:start' // <Handler.TraceBase>
  'course:route:stop' // <Handler.TraceBase>
  'course:route:update' // <LayerGroup.Route>
  'course:route:new' // <LayerGroup.Route>
  'course:route:finish' // <LayerGroup.Route>
  'course:route:abort' // <LayerGroup.Route>
  'course:route:resume' // <LayerGroup.Route>

  'course:clear' // nothing

  'course:waypoint:add' // <Marker.Waypoint>
  'course:waypoint:move' // <Marker.Waypoint>
  'course:waypoint:remove' // <Marker.Waypoint>
  'course:waypoint:update' // <Marker.Waypoint>

  'course:bearing:start' // <Handler.BrearingBase>
  'course:bearing:stop' // <Handler.BrearingBase>
  'course:bearing:add' //FIXME
  'course:bearing:move' //FIXME
  'course:bearing:remove' //FIXME

  'course:track:start' // <Handler.TrackBase>
  'course:track:stop' // <Handler.TrackBase>
  'course:track:found' // <GeolocationPosition>
  'course:track:error' // <GeolocationPositionError>
);

Please take a look at the Demo each event is printed in the console.

Helpers

the library come with some handy function to help you.

L.Control.Course.format(number, unit); // convert and format values issued from leaflet  Control.Course or Geolocation API to the desired units. supports km, mi, NM, km/h, ft/min, kt, ft, °, time default to m
L.Control.Course.extract(data); // from (<Marker.Waypoint> | <Marker.Bearing> | <Marker.Trackpoint>).data . Filter and Format the data to print them elsewhere.

Extend the plugin with new tool

It is pretty easy to extend the plugin with new tools. You basicaly need to do two things :

  • add your tool part in options
{
  tools: {
    //...
    mytool: {
      icon: 'A',
      title: 'My new Tool',
      handler: L.Handler.MyToolBase
    }
  }
}
  • Create a new handler L.Handler.MyToolBase
L.Handler.MyToolBase = L.Handler.extend({
initialize: function (map, control, options) {
  L.Util.setOptions(this, options);
  this._map = map;
  this._control = control;
  // host & initialize values here.
},
addHooks: function() {
  this.target.style.filter = 'invert(1)';
  // Set your behaviour here.
  this._map.fire('course:mytool:start', this); // fire some event is a good practice
},
removeHooks: function() {
  this.target.style.filter = 'invert(0)';
  // Unset your behaviour here.
  this._map.fire('course:mytool:stop', this); // fire some event is a good practice
},
});

for a pretty simple example see Clear tool which remove anything drawn with this control from the map.

for a more complex one see the Route tool which is a suite of handlers describing the several states needed to create and edit a route.