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

Refactor Source interface to allow/prepare for extensibility #2667

Merged
merged 68 commits into from
Jul 20, 2016

Conversation

anandthakker
Copy link
Contributor

@anandthakker anandthakker commented Jun 3, 2016

Replaces #2648

  • Inverts TilePyramid and Source dependencies. TilePyramid now essentially serves as a common wrapper class for dealing with sources.
  • Renames TilePyramid to SourceCache. This is partly aspirational, and partly to make it more reasonable to have things called source that are actually TilePyramids 😁
  • Eliminates the ability to create a Source independently of a Map instance - i.e., no more new mapboxgl.GeoJSONSource(); sources must be created with map.addSource(id, json).
  • Eliminates bucketStats, as it is no longer needed (so @lucaswoj assures me)
  • Source types are now classes with constructor signature (id, options, dispatcher) => Source and an optional workerSourceURL static property
  • The Source provided by the factory function is an object implementing:
id: string
tileSize: number
minzoom: number
maxzoom: number
roundZoom: boolean
reparseOverscaled: boolean
isTileClipped: boolean

loadTile: (tile: Tile, cb: (err, ?data) => void) => void
abortTile: (tile: Tile) => void
unloadTile: (tile: Tile) => void
serialize: () => Object // plain JS
?prepare: () => void

// @fires `load` to indicate source data has been loaded, so that it's okay to call `loadTile`
// @fires `change` to indicate source data has changed, so that any current caches should be flushed 
  • WorkerSourceURL points to a script, which, when executed in the worker thread, must call self.registerWorkerSource(WorkerSource), where WorkerSource is a class that the worker will construct with new WorkerSource(actor, styleLayers).

WorkerSource instances may implement a loadTile method; if they do, then this will be used by the worker when its 'load tile' target is invoked with a type: 'source-type' parameter (e.g. type: 'geojson'). Any other methods on workerSource instance can be targeted by the main-thread Source via dispatcher.send('source-type.methodname', params, callback).

Questions:

  • Does the above design have broad (enough) approval?
  • The Tile objects are used by various source types to maintain tile-specific state; ideally, this implicit contract between TilePyramid and Source should be made explicit in the Source interface... but I think this is perhaps out of scope for this particular PR - perhaps this would be naturally addressed in Implement cross-source symbol placement #2703, or else could be ticketed separately?
  • I'm not sure what the data yielded by loadTile should look like. Currently, the only thing I can see being needed by code outside of the particular source is bucketStats, which the pyramid uses to emit the tile.stats event. @lucaswoj can you weigh on in this?

Remaining Tasks:

  • Video source - @lucaswoj if you can help me get this working, I'm assuming I can probably then manage Image source.
  • Image source
  • Settle Geojson Source design
  • Document the Source interface
  • Document WorkerSource
  • Document addSourceType -- includes documenting the create function and the self.registerWorkerSource(...) requirement on WorkerSource modules.
  • Document "How to implement a source type" -- there's probably a bit more to say beyond the Source, WorkerSource, and addSourceType. Perhaps an example of a 3rd-party source type would suffice. update: went with a link to https://github.com/developmentseed/mapbox-gl-topojson in the docs for GeoJSONWorkerSource.
  • Update docs for ImageSource, VideoSource, GeoJSONSource to reflect that their constructors are no longer publicly available
  • Fix any test regressions
  • Add new unit tests as appropriate
  • Cleanup - drop async loading of core source types, and do it in the "right" place.
  • Cleanup - geojson is a core source type, and so I think we should hard-code require its worker source implementation directly in worker.js--in addition to avoiding the function (self) {} wrapper, it would be silly to webworkify it and waste time at runtime sending that code to all the worker threads
  • Cleanup - move loadTileJSON, queryRenderedFeatures, and querySourceFeatures out of source.js
  • Cleanup - fix upstream bug in WebWorkify
  • Cleanup - hunt & remove all TODO

@@ -66,12 +72,36 @@ function Style(stylesheet, animationLoop) {
this.glyphSource = new GlyphSource(stylesheet.glyphs);
this._resolve();
this.fire('load');

if (validateStyle.emitErrors(this, validateStyle(stylesheet))) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did this move? Don't we want to continue to short circuit if there's a validation error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, yup - not quite sure when/how this happened, but it's a mistake. Thanks!

@jfirebaugh
Copy link
Contributor

Regarding Source.is, addSource, and createSource: I think we could take either of the following approaches:

  • Remove the ability to create or add a source other than addSource(id, json), where json is what you'd see in the style spec. (No more new mbgl.GeoJSONSource(), etc.)
  • Not go the factory function route just yet. Do whatever additional configuration is needed (e.g. setting dispatcher) on the source only when addSource is called.

@jfirebaugh
Copy link
Contributor

It's weird to rely on sources presenting an id property with the correct value.

Seems fine to me. The reason that id is a separate parameter in addSource, and not included in the object like it is with layers, is that the style spec uses an id-keyed object rather than an array. This is probably a design mistake and we should been consistent: use arrays, validate id uniqueness externally. Anyway, I think of id as being an intrinsic property for both layers and sources.

@@ -68,10 +76,32 @@ function Style(stylesheet, animationLoop) {
this.fire('load');
}.bind(this);

if (typeof stylesheet === 'string') {
ajax.getJSON(normalizeURL(stylesheet), loaded);
var sourceTypesLoaded = function(err) {
Copy link
Contributor

@jfirebaugh jfirebaugh Jun 3, 2016

Choose a reason for hiding this comment

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

Can you explain this part of the code? It looks like it's loading support for core source types asynchronously? That seems odd to me, both that it should be async, and that something like addSourceType should be the responsibility of Style and attached as a non-underscored method.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would this comment tie in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jfirebaugh Yeah, once this refactor settles, I think this async weirdness can be eliminated. It's here now only as a quick and dirty way to validate/show that addSourceType works, but ultimately there's no reason that core source types need to be added asynchronously through the addSourceType interface.

I do think addSourceType should:

  • (a) be a public, non-underscored method -- because its purpose is to allow third-party source types can be plugged in, which is the motivating reason for this PR; and
  • (b) be async -- because if a source type needs to provide a WorkerSource implementation, then the process of having all the workers load that implementation via importScript is async.

@mourner
Copy link
Member

mourner commented Jun 7, 2016

I'm still not sure if we want to have geojson-clustered as a separate type for clustering, and generally have different type names for the same source with slightly different interpretation. I'm more comfortable with the current API because the source is GeoJSON whether clustered or not — clustering is a matter of interpretation. Suppose we want to make a plugin for clustered TopoJSON — would we have a separate source named topojson-clustered and somehow reuse parts of both geojson-clustered and topojson sources?

@anandthakker
Copy link
Contributor Author

@mourner I definitely see what you're saying, but I'm not 100% sold on the idea that different source types should only map to the format of the original source data being used. I agree that clustering is an interpretation of the data; but then would hex-bins and square-bins also just be interpretations that belong as options on GeoJSONSource?


if (source.id !== id) {
throw new Error('Expected Source id to be ' + id + ' instead of ' + source.id);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I think it probably just makes more sense to do source.id = id here.

@anandthakker
Copy link
Contributor Author

@lucaswoj @jfirebaugh @mourner Updated the top of the ticket with more complete description of the overall design and capturing outstanding questions/tasks from the reviews thus far (so we should now be okay to rebase w/o losing key discussion points).

@anandthakker
Copy link
Contributor Author

Transcribing from a diff comment:

@jfirebaugh : [It] seems odd to me, both that it should be async, and that something like addSourceType should be the responsibility of Style and attached as a non-underscored method.

@anandthakker : I do think addSourceType should:

  • (a) be a public, non-underscored method -- because its purpose is to allow third-party source types can be plugged in, which is the motivating reason for this PR; and
  • (b) be async -- because if a source type needs to provide a WorkerSource implementation, then the process of having all the workers load that implementation via importScript is async.

Lucas Wojciechowski added 2 commits July 20, 2016 13:07
We should validate the custom source architecture and build some demo
sources before documenting the internals publicly.
@lucaswoj lucaswoj assigned lucaswoj and unassigned lucaswoj Jul 20, 2016
@lucaswoj lucaswoj merged commit def9cb5 into master Jul 20, 2016
@lucaswoj lucaswoj deleted the source-refactor branch July 20, 2016 21:44
@anandthakker
Copy link
Contributor Author

🎉

@mourner
Copy link
Member

mourner commented Jul 22, 2016

@lucaswoj For some reason, the build fails in master since the merge: https://circleci.com/gh/mapbox/mapbox-gl-js/4209

@lucaswoj
Copy link
Contributor

Thanks for the heads-up @mourner. I'll try to resolve ASAP.

@hdrdavies
Copy link

@mourner how can one now configure clustering as was possible before i.e.

        let sourceObj = {
            type: 'foo',
            cluster: true,
            clusterRadius: 50,
            clusterMaxZoom: 14,
            data
        };

        map.addSource('foo', sourceObj);

@mourner
Copy link
Member

mourner commented Aug 17, 2016

@hdrdavies same as before, as you can see on the example https://www.mapbox.com/mapbox-gl-js/example/cluster/

@hdrdavies
Copy link

@mourner thanks very much!! It seems I'm getting other strange constructor errors even though I'm not using new GeoJsonSource anymore...

Do you think you guys will be adding support for the clustering of custom HTML markers made using new mapboxgl.Marker ? Maybe I'm missing something in the documentation. Apologies if so.

@mourner
Copy link
Member

mourner commented Aug 18, 2016

@hdrdavies currently no plans to do this for the HTML markers — this would be a non-trivial effort. Perhaps someone will contribute a plugin.

@lucaswoj
Copy link
Contributor

Thanks @mourner. @hdrdavies, you might be able to write your own HTML clustering code using the same backend as GL JS https://github.com/mapbox/supercluster

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.

None yet

5 participants