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

OGC Features Provider and Features Layer #12493

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft

OGC Features Provider and Features Layer #12493

wants to merge 11 commits into from

Conversation

jjspace
Copy link
Contributor

@jjspace jjspace commented Feb 24, 2025

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

Author checklist

  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

@jjspace jjspace requested a review from ggetz February 24, 2025 19:43
Copy link

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;
Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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.


Object.defineProperties(FeatureProvider.prototype, {});

FeatureProvider.prototype.requestFeatures = function (bbox, time) {
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor

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?

OgcFeatureProvider.prototype.requestFeatures = async function ({
bbox,
time,
partialLoadCallback,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be defined in FeatureProvider even if it's a no-op for certain implementations.

Also consider exposing events on FeatureProvider like featuresLoaded which can be subscribed to by the FeatureLayer.

/**
* @param {Feature} feature
*/
FeatureLayer.prototype.add = function (feature) {
Copy link
Contributor

@ggetz ggetz Feb 25, 2025

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();
Copy link
Contributor

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.

Copy link
Contributor

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;

Copy link
Contributor

@ggetz ggetz Feb 25, 2025

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}
Copy link
Contributor

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();
Copy link
Contributor

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;
Copy link
Contributor

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.

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.

Support OGC API - Features
2 participants