Background
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
July 2, 2014 - MapKnitter Annotations: Textbox Rotation using CSS Transforms
July 14, 2014 - MapKnitter Annotations: Leaflet.Illustrate.EditHandle and Leaflet.Illustrate.Pointer
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/:
Progress
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.tooltipText
to guide users through the annotation process Leaflet.Illustrate.PointerHandle
should be refactored into two subclasses:Leaflet.Illustrate.PointerVertexHandle
andLeaflet.Illustrate.PointerMidpointHandle
- All
Leaflet.Illustrate
code should be reviewed for instances ofL.Point
arithmetic, and, where possible, arithmetic methods should be replaced by their underscore-prefixed cousins (i.e.L.Point._add
should be used instead ofL.Point.add
). This is purely for efficiency.
Here's what I've been up to since I last posted:
Leaflet.Illustrate.Textbox
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 L.DomEvent.stopPropagation
on mousedown
, mousemove
, and 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 L.DomUtil.enableTextSelection
directly.
Leaflet.Illustrate.Pointer
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 L.Illustrate.Pointer
:
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 <g>
or <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.
In other respects, much of the code for L.Illustrate.Pointer
is borrowed directly from L.Path
, L.Path.SVG
, and L.Polyline
when it was not possible for it to inherit from them explicitly.
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 drag
or click
events on a handle call the _addVertex
, _removeVertex
, or _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:add-vertex
, edit:remove-vertex
, or 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.Pointer
and 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.
Miscellaneous Updates
Since my last research note, I completed a number of housekeeping tasks:
- Connected
Leaflet.Illustrate
repo to Travis CI - Connected
Leaflet.Illustrate
repo to Coveralls - Published
Leaflet.Illustrate
tonpm
asleaflet-illustrate
- Added release tags for v0.0.1 and v0.0.2 in git
- Added build, coverage, and version badges to the
README
.
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
Leaflet.Illustrate
into MapKnitter. - Refactor
L.Illustrate.PointerHandle
- Extend
L.Illustrate.Toolbar
to provide an intuitive interface for users to change the color, font, font-size, and other styles on the textbox and other features. - Implement
toGeoJSON
methods forL.Illustrate.Pointer
andL.Illustrate.Textbox
(will probably usetoGeoJSON
to 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
7 Comments
This is great! We have a workshop this Friday, will we be able to use these features? We have photos and text to add.
Is this a question? Click here to post it to the Questions page.
Reply to this comment...
Log in to comment
liz - I'll do my best to get it working by Friday. In order to get a bare-bones installation plugged in to mapknitter, I'll need to write some
.erb
templates and add some tables to the database. The database will probably be the most complicated.I'm new to rails, so it may take me some time - I'll let you know where I stand on Thursday. I hope I can get it working by Friday - I'd love to get some feedback ASAP.
Reply to this comment...
Log in to comment
Wow cool Justin. Yes, just let me know on Thursday. For now, we'll plan to simply add annotations of the style that already exist, like adding points and embedding photos and text to be shown in pop-up windows. So no pressure, but we are eagerly anticipating these features! :D
Reply to this comment...
Log in to comment
Hi Justin, thanks for the update... Are you planning to use the existing data models in the annotation feature on mapknitter for the database storage? If that works it may save you time. Will previously made annotations be ported to Leaflet.Illustrate?
Thanks!
Is this a question? Click here to post it to the Questions page.
Reply to this comment...
Log in to comment
liz - doesn't look like I'll be able to put anything together for you guys tomorrow (among other things, I had forgotten that the main editing page is still running OpenLayers, not Leaflet). Hopefully I'll have something basic up soon, though, so folks can start beta-testing!
jeff - Not sure about using existing data models. Annotations that have already been created will be preserved - either by porting them to L.Illustrate, or by maintaining them as markers + popups (the current status quo).
Reply to this comment...
Log in to comment
Ok thanks for letting us know! :)
Reply to this comment...
Log in to comment
Justin, thanks for all this, i'll be running smack into teaching mapknitter early september. apologies if i haven't been interacting with your work, but i'm very grateful for it!
Reply to this comment...
Log in to comment
Login to comment.