Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vectors 2.0 #2290

Merged
merged 63 commits into from
Dec 18, 2013
Merged

Vectors 2.0 #2290

merged 63 commits into from
Dec 18, 2013

Conversation

mourner
Copy link
Member

@mourner mourner commented Dec 12, 2013

Major rewrite of Leaflet vector layers, #155. The goal is to refactor vector layers code in Leaflet to make it possible to switch between rendering backends (Canvas, SVG, etc.) dynamically, have more than one physical pane for vector layers for advanced stuff (layering etc.), get rid of ugly hacks, and make the code much cleaner and more transparent.

Another nice additions include massive performance boost for Canvas layers (in cases like updating shape style on hover), retina support for Canvas, proper MultiPoly implementations (one path with several rings instead of FeatureGroup-based), poly getCenter that returns proper centroids and is used by path.openPopup and much faster (cached) poly getBounds.

This also potentially opens it up for interesting future extensions, like custom shapes, indexing layers with RBush for fast interaction features, etc.

  • Renderer — extends Layer, handles positioning, bounds and zoom animation for an SVG/VML/Canvas root
  • SVG — extends Renderer, adds SVG root and handles generic SVG stuff (elements, styles, events, etc.)
  • SVG multidimentional points to SVG paths for polygons/polylines/multipoly
  • SVG point/radius into SVG circle path
  • SVG mouse events
  • SVG brintToBack/bringToFront
  • SVG.VML — patches SVG with VML-specific code (since other code is pretty similar)
  • Canvas — extends Renderer, adds Canvas root and handles generic Canvas stuff
  • Canvas drawing polylines/polygons/multipoly from multidimensional points on canvas
  • Canvas drawing circles from point/radius on canvas
  • Canvas mouse events
  • Path — extends Layer, base class for all vector layers (without renderer-specific code)
  • Path popup methods
  • Polyline — extends Path, projects/clips/simplifies latlngs, delegates drawing to renderer
  • Polyline closestLayerPoint
  • Polygon — extends Polyline, clips polygon latlngs, delegates drawing to renderer
  • MultiPolyline — turned out to be unnecessary
  • MultiPolygon — turned out to be unnecessary
  • test for input to polys with various rings
  • toGeoJSON for polylines/polygons/multipolys
  • Rectangle — extends Polygon
  • CircleMarker — extends Path, delegates drawing to renderer
  • Circle — extends Path, scales radius, delegates drawing to renderer
  • Retina support for Canvas
  • Canvas brintToBack/bringToFront not currently possible because of how events work
  • centroids and proper openPopup for polylines/polygons/multipolys?
  • proper poly getBounds that takes rings into account
  • optimize Canvas mouse events performance
  • test in IE
  • go through code and add some comments
  • update changelog with the changes
  • start an upgrading guide for 0.8

@mourner
Copy link
Member Author

mourner commented Dec 12, 2013

Polyline works (with SVG renderer) now! The work will go pretty smooth from now on I think.

@mourner
Copy link
Member Author

mourner commented Dec 13, 2013

@danzel @tmcw @jfirebaugh This is pretty much finished except some minor stuff — all tests pass and previous vector examples work, so I would really appraciate some feedback — feel free to review.

@mourner
Copy link
Member Author

mourner commented Dec 13, 2013

OK, this epic refactoring is done. Everything seems to work. Now waiting for feedback.

@mourner
Copy link
Member Author

mourner commented Dec 14, 2013

@jacobtoye Jacob, would love to have you review this epic pull! It may affect Leaflet.draw a lot.

@danzel
Copy link
Member

danzel commented Dec 15, 2013

An expected bug:
If you switch the order the layers are added in the new vector2 example, you can't mouseover or click the svg as the canvas covers everything.
Swap lines 32-35 with 27-30.

@danzel
Copy link
Member

danzel commented Dec 15, 2013

It is very cool being able to have multiple canvas/svg renderer layers!

// click on polygon border
if (L.Polyline.prototype._containsPoint.call(this, p, true)) { return true; }

// ray casting algorithm for detecting if point is in polygon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more likely the mouse will be inside the polygon than on the line, could move this before the polyline check.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch!

@danzel
Copy link
Member

danzel commented Dec 15, 2013

Have played with this and had a rough read over, haven't noticed anything obviously wrong and it seems to work :)

@danzel
Copy link
Member

danzel commented Dec 15, 2013

Actually, found something that doesn't work:
https://gist.github.com/danzel/9f81402f2c8818d68480
Use the layers control.

If you remove and re-add canvas or svg it doesn't reappear (This might not be supported, just thought I'd try)

Removing path makes a crash
Removing circle removes the path too (re-adding it doesn't re-add the path)

@jacobtoye
Copy link
Member

Sweet refactor @mourner.

It breaks Leaflet,draw slightly but shouldn't be too hard to fix. I'll create a new branch to fix. When do you think you'll merge to Leaflet master? (roughly)

@mourner
Copy link
Member Author

mourner commented Dec 16, 2013

@jacobtoye awesome! I'll merge in a few days maybe, after figuring out the issues Dave pointed out.


if (this._latlngs.length) {
this._pxBounds = new L.Bounds(
this._map.latLngToLayerPoint(this._bounds.getNorthWest()).subtract(p),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create an empty L.Polyline without adding to the map, add latlngs then then try to add to the map this._bounds is an empty L.LatLngBounds object. Therefore this._bounds.getNorthWest() will error.

This is because L.Path.redraw doesn't call L.Renderer._update since this._map doesn't exist (as it hasn't been added to the map yet).

E.g.

var a = L.polyline([]);
a.addLatLng([0,0]);a.addLatLng([1,1]);a.addLatLng([1,2]);
map.addLayer(a);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not because of redraw, it's because this._bounds is extended when doing _convertLatLngs, and addLatLng doesn't use this, so we need to extend there too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now, please check it out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if this is somehow related to an issue(this._map not existing) that I am having in Draw + 0.8-dev where moving the mouse after "draw-created" or "draw-stop" throws Uncaught TypeError: Cannot call method '_fireMouseEvent' of null from https://github.com/Leaflet/Leaflet/blob/master/src/layer/marker/Marker.js#L231 .

I have a branch here to illustrate this with, just check out the basic demo.

https://github.com/snkashis/Leaflet.draw/tree/0.8_testing

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

@danzel OK fixed everything that you've mentioned (unclickable SVG, adding/removing, etc.) — we're good!

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

@jacobtoye also pay attention to 5af4f0e, you'll have to fix this across Leaflet.draw code.

@danzel
Copy link
Member

danzel commented Dec 17, 2013

Looks good from the diffs

@jacobtoye
Copy link
Member

OK Leaflet.draw should be fine with this branch. Only issues were the one I mentioned in the commit comment, one around dash arrays and the L.DomEvent.off requiring context. Should be good to go :)

@jacobtoye
Copy link
Member

I'll check Leaflet.label too. I have a feeling that it will also be broken ;)

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

I think the branch is ready to merge :) Excited!

@jacobtoye
Copy link
Member

Sweet Leaflet.label works :)

Really like this refactor. Hopefully there is not too many complaints about moving support for multipolys from L.MultiPoly to the other vectors.

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

Ha, not yet ready! Wait till I commit an even more awesome improvement — partial redraw of canvas vectors that makes things like interactive choropleth maps with shape hover many times faster!

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

@danzel @jacobtoye now check the last commit — it should make a massive performance boost when using lots of canvas layers that update on hover (tested on choropleth of ~1800 zip code shapes, looking smooth).

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

@jacobtoye @danzel btw, what do you think about getting rid of MultiPolyline & MultiPolygon? The rationale for this is that after refactoring of vector code, implementations of multipoly collapsed into L.MultiPolyline = L.Polyline.extend();, because it was much easier and simpler to integrate multiple rings handling in Polyline that patch it up in children (e.g. logic for Polygon holes and multiple polyline rings is the same, etc.). So I removed that and also added some heuristics to toGeoJSON code to roundtrip properly. And I like the simplicity of not having separate classes that do almost the same.

@patrickarlt
Copy link
Member

I like having MultiPolyline & MultiPolygon from an documentation standpoint since people would know where to look if they have a need to render Multi* objects.

If the classes were merged would this mean that

polyline.setLatLngs([
 [ latlng1,  latlng2,  latlng3,  latlng4 ]
]);

and

polyline.setLatLngs( [ latlng1,  latlng2,  latlng3,  latlng4 ]);

would achieve the same thing? I can see how users might find that confusing.

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

@patrickarlt we could specifically include a note on Multi-features in documentation so that people could immediately find it in the reference.

Yeah, visually those two lines would lead to the same, but I don't see that as a problem for users.

My main point for ditching user-facing Multi-classes is that API should reflect implementation, we don't want to have bogus classes just for the sake of "Multi" in the name — that would be confusing for sure.

@jacobtoye
Copy link
Member

@mourner I agree with removing the Multi* classes. However it will obviously effect people who have updated to the newest Leaflet without being aware of the changes.

Perhaps you could deprecate the classes for a version 0.8 -> '0.9'? Not sure the best way to do this, maybe just documentation. Could use console.warn if browser supports it?

@mourner
Copy link
Member Author

mourner commented Dec 17, 2013

@jacobtoye 0.8 will be pretty breaking in small details like this even without multipoly changes, so it seems to me that it would be less painful to break all at once, provided we have a good written upgrade guide. It also seems that average users don't instantiate MultiPoly objects often.

@mourner
Copy link
Member Author

mourner commented Dec 18, 2013

TOTAL APOCALYPSE NOW

@jacobtoye
Copy link
Member

Some people just want to watch the world burn... ;)

Great work @mourner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants