I am working to add rich annotation functionality to MapKnitter as part of Google Summer of Code (read about my project here: http://publiclab.org/notes/justinmanley/03-18-2014/mapknitter-annotations-using-fabric-js-gsoc-2014-proposal). This is my sixth week of coding. The purpose of this research note is to provide an update on my progress with implementing text annotation for Leaflet.
To trace the discussions that have led me up to this point, you can look back through the following research notes, listed in chronological order.
March 18, 2014 - MapKnitter Annotations Using Fabric.js (GSoC 2014 Proposal)
June 17, 2014 - MapKnitter Annotations Plugin: Preliminary Specification
June 25, 2014 - MapKnitter Annotations Update: L.Illustrate.Textbox
You can view the code for Leaflet.Illustrate on GitHub and you can view a demo using the version of Leaflet.Illustrate (0.0.2) released along with this research note on GitHub at http://manleyjster.github.io/Leaflet.Illustrate/examples/0.0.2/simple/:
Leaflet.Illustrate is done!
Well, maybe not done.
Leaflet.Illustrate has reached the point where it can provide basic text annotation capability to the MapKnitter community, so I figure it's a good time to start integrating it with MapKnitter. There's still much that can be done to improve
Leaflet.Illustrate to ensure that it is a fast, robust, and dependable library for Leaflet text annotation. Specifically:
- Tooltip text should be added to
Leaflet.Illustrate.tooltipTextto guide users through the annotation process
Leaflet.Illustrate.PointerHandleshould be refactored into two subclasses:
Leaflet.Illustratecode should be reviewed for instances of
L.Pointarithmetic, and, where possible, arithmetic methods should be replaced by their underscore-prefixed cousins (i.e.
L.Point._addshould be used instead of
L.Point.add). This is purely for efficiency.
Here's what I've been up to since I last posted:
I fixed the annoying flickering with the pointer connecting the rotate handle to the textbox in editing mode by making the pointer an instance of
L.Illustrate.Pointer. Of course, this required implementing
L.Illustrate.Pointer from scratch - more on this below.
I was also able to fix text selection and cursor placement in textboxes by calling
mouseup events from the
<textarea>, thanks to a helpful suggestion from @danzel on the GitHub issue I posted: How do I get text selection working for a textarea on top of the map?.
I encapsulated this functionality as
L.Illustrate.Selectable. However, I still feel (as I wrote in the GitHub issue thread) that
L.Illustrate.Selectable should be unecessary, as this should be taken care of by
map.dragging.disable() or by calling
I spent a lot of my time over the past two weeks implementing
L.Illustrate.Pointer. A pointer is a line with only a single geographically-fixed point (the "anchor" point). The coordinates of the line are given in pixels with respect to the anchor point. The line thus maintains constant size through zoom events (it does not scale with the map):
new L.Illustrate.Pointer( <LatLng> anchor, <Point> coordinates, <Polyline options> options? )
The immediate purpose for the
L.Illustrate.Pointer class was to provide a robust pointer for
L.Illustrate.Textbox (see above), but
L.Illustrate.Pointer can serve as a general-purpose pointer linking any large annotation to a single point on the map.
A few notes on the design of
Each instance of
L.Illustrate.Pointer has its own containing
<svg> element. This was done so that pointers would zoom smoothly using CSS3 transitions (CSS properties can only be applied to
<svg> elements, and not their
<path> children), since pointers with different anchor points would require different
css-transform translations. It is possible that this will raise performance issues if there are lots of pointers on the map, but I think that for most of our use cases it will present no issues.
As it turns out, the editing handles for
L.Illustrate.Pointer were much more difficult to design than those for
L.Illustrate.Textbox. This is because the position of an editing handle for
L.Illustrate.Textbox is fixed: a handle which starts out at the upper left corner will always be at the upper left corner (disregarding rotations). Editing handles for polylines, by contrast, can change position as vertices are added and deleted - and throughout, they must be in exact correspondence with the coordinates of the polyline so that dragging a handle updates the correct vertex.
My approach, similar to the approach I used for
L.Illustrate.RotateHandle, etc. - but different from @jacobtoye's approach in Leaflet.draw, was to delegate as much as possible of the UI logic to the handles themselves.
The way I ended up implementing this was that
click events on a handle call the
_updateVertex methods of
L.Illustrate.Edit.Pointer. These methods of
L.Illustrate.Edit.Pointer handle the creation and destruction of instances of
L.Illustrate.PointerHandle as necessary to add and remove vertices from the pointer. Once this is complete, the method fires an
edit:update-vertex event. Instances of
L.Illustrate.PointerHandle listen for these events and each is responsible for updating its own
_id property (the
_id property indicates which vertex the handle controls).
My hope was that implementing editing handles in this way would enable the editing handles to be highly decoupled from
L.Illustrate.Edit.Pointer. I think that the implementation was relatively successful from that perspective - but it is excessively complicated by the need for each handle to maintain an accurate internal
_id property linking it to the correct coordinate. This might be simplified by splitting
L.Illustrate.PointerHandle into two separate classes and giving each vertex handle responsibility for the midpoint handles to either side.
Since my last research note, I completed a number of housekeeping tasks:
Leaflet.Illustraterepo to Travis CI
Leaflet.Illustraterepo to Coveralls
- Added release tags for v0.0.1 and v0.0.2 in git
- Added build, coverage, and version badges to the
It is my hope that these additions will give others confidence in the quality of this Leaflet plugin and encourage them to contribute or to use it for their own projects.
Next Steps & Future Improvements
- Fork MapKnitter repo and begin working on integrating
L.Illustrate.Toolbarto provide an intuitive interface for users to change the color, font, font-size, and other styles on the textbox and other features.
L.Illustrate.Textbox(will probably use
toGeoJSONto serialize these objects for storage in the MapKnitter database)
- Allow textboxes to be fixed at points other than their center points
- Fix feature deletion button in toolbar