-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
OGC Features Provider and Features Layer #12493
base: main
Are you sure you want to change the base?
Conversation
Thank you for the pull request, @jjspace! ✅ We can confirm we have a CLA on file for you. |
* @param {Object} options | ||
*/ | ||
function FeatureProvider(options) { | ||
this.id = options.id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've had some pain points with inheritance in the past. To avoid these, I would recommend treating FeatureProvider
as an interface as much as possible. That is, I would avoid defining any implementation details, and leave it to the implementor or static utility-like functions.
// this._itemsLoaded < this.maxItems | ||
itemsLoadedThisRequest < this.maxItems | ||
) { | ||
if (localAbortController.signal.aborted) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify my understanding, the abort controller as it is used in this class is not actively cancelling an in-progress requests— Only preventing more requests from happening. Is that correct?
As a follow up question, is this something important we need to consider for the Resource
implementation? Would it fit in with the existing implementation, or would it necessitate more significant refactoring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify my understanding, the abort controller as it is used in this class is not actively cancelling an in-progress requests— Only preventing more requests from happening. Is that correct?
Correct, as currently written this just stops the loop and future requests from starting and ignores the results. I implemented this first and it basically is just acting as a nicer wrapper for toggling a boolean for previous calls to this function. I added the cancelResource
function later to try and actually handle canceling the underlying network requests themselves and prevent the browser itself from blocking. There's probably a cleaner way to do this but "it worked" for now.
The AbortController
and signal is designed to actually be used with the native fetch
(I picked up the pattern when working on a vscode extension). fetch
has an option to pass the signal
in when it's created and it will just handle canceling the underlying request. This won't work for us because Resource
is still built around XHR requests which do not have this functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I'd still hear any thoughts around the existing Resource
implementation. Are we at the point where it's enough of a blocker that we should be considering a refactor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I somehow missed that you can addEventListener
to an AbortSignal
. I'm pretty sure this lets us augment Resource
/Request
to take a signal
parameter like fetch
can and have it cancel the requests when the signal is aborted. I've implemented this for a few of the Resource.fetch**
calls in my latest commits and it works. Take a look and see if this makes sense to you then I can do the rest.
It may even make sense to do a dedicated PR for this update to Resource
that I can then pull into this branch, open to ideas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may even make sense to do a dedicated PR for this update to Resource that I can then pull into this branch
That sounds like a great plan to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also saw that your changes may help us remove the need to use defer.js
.
That file was a compromise from the when to promise refactor, and we'd like to get rid of it ASAP. I think Resource
, RequestScheduler
, and KmlDataSource
are the last bastions of using defer.js
.
|
||
Object.defineProperties(FeatureProvider.prototype, {}); | ||
|
||
FeatureProvider.prototype.requestFeatures = function (bbox, time) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if the bbox
parameter should be a bit more generic. While bbox is very straightforward for the OGC features API, it may not map to other providers as well such as something more tile-based.
I'm trying to think through what may be needed to determine the proper request... Perhaps camera (position, direction, and frustum extents), scene mode, ellipsoid... I'm trying to avoid something as low-level as passing in the FrameState
as we do in many places, but that may be a possibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in person I believe this will be replaced with the full camera
object so any implementations can calculate a bbox or other features from that.
/** | ||
* A class for things | ||
* @constructor | ||
* @param {FeatureProvider | OGCFeaturesProvider} provider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If OGCFeaturesProvider
implements FeatureProvieder
, I don't think we'd need to specify the type explicitly, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically no. However as previously discussed at length TS will not recognize inheritance that's defined purely through JSDoc so it doesn't understand this without both. I have been adding JSDoc comments for types to help me while I develop but may need to do a pass on all of them before this is merged to clean them up to be "correct"
), | ||
); | ||
|
||
if (distance > 400) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are camera moved events on Camera.js
. Perhaps they are sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a look at the move events but wasn't sure that relying on moveEnd
was the right play here. I didn't dig into it too much yet though.
That said, I pushed up logic for tracking what tiles have been requested from the provider level. They keep track of which ones are active
and if they're already active it doesn't try to re-request them. I think this may work well enough to prevent restarting requests that are still visible making this sort of check less important. Let me know your thoughts?
/** | ||
* @param {Feature} feature | ||
*/ | ||
FeatureLayer.prototype.add = function (feature) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll probably want remove
and removeAll
functions as well.
// this._primitives = this._dataSource.entities; | ||
this._primitives = []; | ||
|
||
this.features = options.features ?? new AssociativeArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find an issue for it, but AssociativeArray
can probably be replaced with native Map
at this point. It's easy an efficient to pull the values as an array through built-in methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, and perhaps more importantly, we should be smart about how we store data for lots and lots of features. There's potential for a lot of duplication across the graphical representation (whether than be Entities or Primitives) and a raw array. We likely want to release the raw features once the representations have been created.
this.id = options.id; | ||
// this.name = options.name; | ||
this._show = options.show ?? true; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this defines a Rectangle
, we should likely be specifying an ellipsoid at this granularity as well so it's meaningful to map the extents to worldspace positions.
* | ||
* @param {GeoJson} geoJson | ||
* @param {ParseOptions} [options] | ||
* @returns {Entity | Entity[] | undefined} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would be possible to map this to a list of Feature
objects? And that, in turn, those could be mapped to Entities in another function to be used GeoJsonDataSource
?
That way, the future implementation could map the returned features to primitives.
// this.name = options.name; | ||
this._show = options.show ?? true; | ||
|
||
this.rectangle = options.rectangle ?? new Rectangle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this rectangle intended to be the extent of the data, or a user defined restriction?
I think these should be separate properties, and the former should probably be read-only via the public API.
*/ | ||
function FeatureProvider(options) { | ||
this.id = options.id; | ||
this.credit = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be implemented such that when the data is in view, credits are added on screen or in the lightbox.
I'm not sure what you plan was here, so I'm just stating this explicitly.
@ggetz I just pushed up the logic for some basic "tiling" of an OGC feature provider to try and break up the area we request. It also tracks which tiles have already been requested to avoid excess requests for areas that are still visible or have been completely loaded already. This seems to help reduce the quantity of requests dramatically. I still have some open questions I need to try and address but take a look at the last commit and see if my current approach is making sense so far.
I also responded to some of the comment threads above so please check those too |
Description
Opening as a draft for early review and feedback around implementation. Will fill out the description more later.
See #12456
Also worth taking a look at @ggetz's
feature-prototype
branch for how we might do some of the lower level stuff.Issue number and link
Resolves #12456
Testing plan
OgcFeatureProvider
andFeatureLayer
classesAuthor checklist
CONTRIBUTORS.md
CHANGES.md
with a short summary of my change