I am working to add rich annotation functionality to MapKnitter as part of Google Sum...
Public Lab is an open community which collaboratively develops accessible, open source, Do-It-Yourself technologies for investigating local environmental health and justice issues.
Public Lab chatroom
Reset your password
Read more: publiclab.org/n/10686
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 fourth 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
You can view the code for Leaflet.Illustrate on GitHub and you can view a demo using the version of Leaflet.Illustrate (0.0.1) released along with this research note on GitHub at http://manleyjster.github.io/Leaflet.Illustrate/examples/0.0.1/simple/:
Since I last posted a research note, I have successfully implemented rotatable textboxes (using CSS transforms as I proposed in a previous research note) which behave correctly and consistently during map zoom and handles for rotating, resizing, and moving textboxes.
In order to implement these features, I have implemented the following classes, many of which are general-purpose extensions of Leaflet core functionality.
L.RotatableMarker (extends L.Marker)
Since textboxes (L.Illustrate.Texbox) are created as instances of L.Marker with HTML icons supplied by L.DivIcon, this is the class which makes textboxes rotatable. The editing handles rotate along with their companion textbox, and so also derive from L.RotatableMarker.
Leaflet.Illustrate.EditHandle (extends L.RotatableMarker)
A handle is a marker attached to a companion shape. A handle:
The key purpose of the Leaflet.Illustrate.EditHandle class is to allow applications to create markers which must maintain a fixed pixel position with respect to a given latlng.
Child classes Leaflet.Illustrate.RotateHandle, Leaflet.Illustrate.ResizeHandle, and Leaflet.Illustrate.MoveHandle all inherit from Leaflet.Illustrate.EditHandle.
I've also implemented several utility methods extending Leaflet core which are worth noting:
Returns a CSS transform string corresponding to the given angle. The units (radians, degrees, turns, or gradians) must be given explicitly. This complements the method L.DomUtil.getTranslateString already defined in L.DomUtil.
L.DomUtil.setTransform(el, point, angle, disable3D)
Sets the transform property on the given HTML element el for the given offset (point), and rotation (angle). If disable3D is set, then there is no rotation (rotation only works in browsers which support CSS transforms).
Leaflet core provides only the method L.DomUtil.setPosition, which sets the absolute position of the given element using either the css-transform translate function or, if disable3D is false, setting the top and left properties on the element. When disable3D is set, L.DomUtil.setPosition just sets the css-transform property to the newly calculated translate string, and, in the process, overwrites any rotation also set using the css-transform property. This new method L.DomUtil.setTransform solves this by setting both the translate and rotate property as appropriate.
There are undoubtedly ways to optimize this. Ultimately, we think about translation and rotation as separate operations, and so it would be nice if the abstraction provided by Leaflet supported this. This could be done by implementing L.DomUtil.setTranslation and L.DomUtil.setRotation, each of which would parse the css-transform string and reset only the parameters of the appropriate translate function. Further (relatively simple) optimizations would be to delete the transform functions when unnecessary (i.e. rotate(0,0,0,0) could be deleted).
I've completed the bulk of the work that has to be done to implement textboxes. There are quite a number of small, but crucial tasks that remain before these textboxes are truly usable.
I've been working for the past few days on eliminating the annoying flickering of the rotate handle pointer on zoom. It's turning out to be quite a thorny problem. Markers (L.Marker) and polylines (L.Path) are implemented very differently. The main difficulty is that each marker has an _animateZoom method which is called when the zoomanim event is fired. In writing L.Illustrate.EditHandle, I simply overrode the default _animateZoom method and reset the position of the handle during zoom in order to maintain constant pixel position with respect to the companion shape of the handle.
Notice how the pointer for the rotate handle (perpendicular blue line on top) flickers on zoom.
Whether the polylines are drawn using Canvas, SVG, or VML (backwards-compatibility with IE), they are scaled during animation using _animatePathZoom, which is a method of the L.Map object, not a method of the polyline. The _animatePathZoom method resets the css-transform of the root <svg> element, translating and scaling all vector objects on the map by the same amount.
This is not what we want for a pointer.
Pointers should extend paths in the same way that handles extend markers. That is, a pointer is a path whose vertices are defined in pixel coordinates, given with respect to an origin which has a fixed geographic position.
After spending a fair amount of time on this, I think that the best way to approach the rotate handle pointer is to define a new class (L.Illustrate.Pointer) to encapsulate this functionality. Since each pointer will be scaled and translated differently during map zooming, each will need to be given its own <svg> root element.
Implementing rotate handle pointers in this way will mean that L.Illustrate.Pointer can also be used for any kind of pointer meant to call out a single location or identify an annotation (like a textbox) with a single point on the map.
Once the Leaflet.Illustrate plugin is usable, I can begin integrating it into MapKnitter. This will involve designing a toolbar (extending either L.Draw.Toolbar or L.Toolbar) to expose the annotation functionality of Leaflet.draw and Leaflet.Illustrate to users and then integrating this toolbar into MapKnitter, among other things.
Hi, Justin - looks great, thanks! I like the attention to detail and appreciate your working to get this right. One thought was that it's a little hard to see the handles -- maybe they could be a little bigger? And while editing the text, could we show a slightly darkened background so you can see how large the area is?
Apologies if this is stuff you just haven't gotten to yet and seems obvious, just trying to be helpful!
Is this a question? Click here to post it to the Questions page.
Looking good! nice to see all the little to dos documented along the way. great update!
You must be logged in to comment.