stamen / modestmaps-js

Modest Maps javascript port
http://modestmaps.com
566 stars 152 forks source link

add core support for multiple tile layers / providers #11

Closed tmcw closed 12 years ago

tmcw commented 13 years ago

Are there any plans to support multiple providers per map? This was, I think, achieved on the FCC project but isn't possible without hacks yet. I'd definitely be interested in helping out if it's not a conscious exclusion.

RandomEtc commented 13 years ago

There are definitely plans! (issue #5 is basically this)

As you say, it's possible now with a few hacks (basically stacking a bunch of Map instances into one container, only giving mouse/touch handlers to the top one, listening for the 'drawn' callback on it and syncing with the other maps). There are a few bugs with the hack approach which were showing up for me in IE8 though - I'd appreciate help tracking those down, I think it might be to do with needing unique IDs for tiles.

In the short term, making a robust LayeredMap object as an example that takes an array of providers and makes the corresponding Maps for you would probably be the right way to help. That way we can work out what methods and interfaces should be (I assume people would want the ability to turn on and off layers, perhaps re-order layers, perhaps set layer opacity on each layer, etc). And the folks who worked on the FCC project can chime in with their thoughts too.

Long term the Map object itself should support more than one provider natively, I think, rather than requiring two separate objects. I'd also Map to be refactored so that each layer can share a single request queue (to limit the number of open tile requests, because some browsers don't allow requests to be canceled and they can quickly add up with layered maps) and a single set of event handlers.

I can pay close attention to this if you're ready to get started, and help out where possible. Feel free to message me directly if you're waiting for input!

migurski commented 13 years ago

Having had some time to play with the results of the UTF Grid experiment I would like to suggest a way forward on this that borrows from Google's v3 Map Types interface. I've chatted with Matthew Bloch about this, and he's recommended it based on his experimentation with canvas and the v3 API.

The short version:

We've already got the provider class in place, which implements many of the same behaviors including tile size and zoom limitations. I'd like to extend it with a new alternative to getTileUrl() called getTileElement(), which returns a DOM element ready for placement on the map and a releaseTileElement() called when it needs to be cleaned up. The decision to use one or the other would be driven by a basic property check: if a provider has the new methods, they'd get used. If it doesn't, getTileUrl() would be used instead. Coming from the Python world this seems quite normal to me - is this kind of duck typing acceptable in JS?

To do multiple layers, the core map object would need to be able to accept a list of providers where it currently has just one. This introduces a bit of weirdness, in that the provider is where the map's projection and coordinate mapping resides. With multiple providers, there could be inconsistencies. Might we establish that first provider is the one that sets the stage for the projection and layout used by the others? Perhaps each subsequent provider is only needed for its tile methods, but not its projection or tile dimensions?

What do we think of a Map.setProviders() method in addition to the existing Map.setProvider() ?

RandomEtc commented 13 years ago

Not sure that support for more than one provider is the same as support for different types of tile renderer, but I'll indulge you...

I think setProvider goes away, and you assume a list of providers, default one. Then you have intuitive listy setter/getters like setProviders(array) getProviders() (returns array) and maybe getProviderAt(index) swapProviders(i,j) removeProviderAt(index) etc. as needed (these don't have to be implemented all at once). Maintaining the API for single providers and multiple providers at once will lead to confusing and unintuitive scenarios - if there's a list of providers already and you call setProvider for example. I also have a fairly strong urge to rename them layers at this point.

I personally like to avoid duck typing as much as possible - in this case mainly to stop MapProvider becoming too much of a monolothic class that also has to handle resources, load queues and such. For the basic URL generating providers we could wrap them in a "painter" object that handles the getTileElement/releaseTileElement stuff and has a load queue that can potentially be shared across layers. This will be an even simpler thing for a layer/painter to render static data, and won't muddle the interfaces that are to do with URLs with the interfaces that are to do with the DOM, which will also be good for server side compatibility.

Since Modest Maps has no history of layers I'd recommend a pretty thorough branch at this point with a lot of the examples rewritten to take advantage of the new functionality - most of the stuff is tuned with one layer in mind (e,g, 4 simultaneous requests) so a lot of those assumptions will need revisiting. But since you (@migurski) were the original author of the majority of the current API, I defer to you on the ModestMapsness of client side tile rendering stuff (recall I removed it from as3 because it was too squirrely...).

[edit, deleted last paragraph because I was repeating myself from earlier in this thread... JFDI is all I'm saying :)]

migurski commented 13 years ago

I'm really liking the array-like providers methods you suggest. Curious to hear from @tmcw about some of these ideas but a JFDI branch would be nice.

migurski commented 13 years ago

About this:

For the basic URL generating providers we could wrap them in a "painter" object that handles the getTileElement/releaseTileElement stuff and has a load queue that can potentially be shared across layers. This will be an even simpler thing for a layer/painter to render static data, and won't muddle the interfaces that are to do with URLs with the interfaces that are to do with the DOM, which will also be good for server side compatibility.

I'm understanding it to mean that the basic providers would not change their interface. For backward compatibility, you'd still be able to write a provider that simply had a getTileUrl() method and the Map would detect that and wrap it up the painter object. Otherwise, it would assume that you had new-style Element-returning bits.

RandomEtc commented 13 years ago

Yeah that's what I was thinking - the detection would probably end up being a one-time thing in setProviders rather than a per-tile duck-typing thing. So not against duck-typing on principle, just trying to move it to less frequently called places.

migurski commented 13 years ago

Definitely agree that the detection would happen when a provider was first added to the Map.

Anyway - it's probably time to stop talking and start coding. If I can swing it I will do a bit of this over the weekend.

migurski commented 13 years ago

I gave the Layers branch a bit of work today: https://github.com/stamen/modestmaps-js/commit/28e6990f

Some parts are working, others are not. In particular I'm getting stuck on the request manager and finding that it's going to need to change somewhat to accommodate being shared, and a number of assumptions about simple image elements in the Map will also need to be changed. It's generally going well, though - just needs a bit more concentrated effort.

migurski commented 13 years ago

Modified the request manager to allow for per-tile callbacks: https://github.com/stamen/modestmaps-js/commit/286d746c#diff-4

migurski commented 13 years ago

Okay, starting to get violent here: https://github.com/stamen/modestmaps-js/commit/416bf1c5

migurski commented 13 years ago

Getting someplace useful: https://github.com/stamen/modestmaps-js/commit/a7712b66

The short story of my changes so far is that we now have a Map object that can support multiple Layers of tiles. Tile providers are now asked to getTile() and releaseTile(), and it's acceptable to return a simple DOM node from the first method for inclusion in the page. Old-style providers with getTileUrl() are detected once, and wrapped up in a TilePaintingProvider. This hasn't been extensively tested, but it's in there.

I was unsuccessful getting RequestManager to play nicely with the Painter-style image provider we discuss above, but I'm actually quite happy with this outcome - the tight coupling of the request manager with the image tiles looks like a result of some fairly serious optimization and compatibility work for older browsers, and I'm basically fine with this.

One thing that still remains is further isolation of non-image tile nodes from the caching parts of the layer, e.g. recentTiles and so forth.

tmcw commented 12 years ago

Okay, consolidating with #67.