Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[core] query source features #8263

Merged
merged 4 commits into from
Mar 9, 2017
Merged

Conversation

ivovandongen
Copy link
Contributor

Fixes #5792

@ivovandongen ivovandongen added Core The cross-platform C++ core, aka mbgl feature ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold labels Mar 2, 2017
@ivovandongen ivovandongen self-assigned this Mar 2, 2017
@mention-bot
Copy link

@ivovandongen, thanks for your PR! By analyzing this pull request, we identified @jfirebaugh, @1ec5 and @incanus to be potential reviewers.

@ivovandongen ivovandongen force-pushed the 5792-query-source-features branch 3 times, most recently from bc88add to d3616d2 Compare March 5, 2017 03:39
@@ -3,6 +3,7 @@
#include <mbgl/util/optional.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/map/query.hpp>
Copy link
Contributor

Choose a reason for hiding this comment

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

This is already included below.

class SourceQueryOptions {
public:
// Required for VectorSource, ignored for GeoJSONSource
optional<std::string> sourceLayer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make this a std::vector<std::string> like RenderedQueryOptions. I don't think there's a good reason that the GL JS API is inconsistent (mapbox/mapbox-gl-js#4372).

AnnotationIDs queryPointAnnotations(const ScreenBox&);
std::vector<Feature> querySourceFeatures(const std::string&, const SourceQueryOptions& options = {});
Copy link
Contributor

@jfirebaugh jfirebaugh Mar 6, 2017

Choose a reason for hiding this comment

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

GL native exposes an object-oriented API for sources -- move this method to mbgl::style::Source, rename it to queryFeatures, and drop the first parameter.

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 This does mean that you have to retrieve the source in the SDKs before querying. Small overhead, but still. Is this what we want?

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 In this case; shall I move SourceQueryOptions to a separate header in the mbgl::style namespace? or keep it here?

Copy link
Contributor

Choose a reason for hiding this comment

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

This does mean that you have to retrieve the source in the SDKs before querying. Small overhead, but still. Is this what we want?

Yes, I think so -- by eliminating the map middleman in map.querySourceFeatures(source), source.queryFeatures() becomes conceptually simpler and possibly actually more efficient in the case of repeated queries (though I would indeed expect the benefit to be negligible).

shall I move SourceQueryOptions to a separate header in the mbgl::style namespace?

Yeah, I think that makes sense.

@1ec5
Copy link
Contributor

1ec5 commented Mar 7, 2017

This does mean that you have to retrieve the source in the SDKs before querying. Small overhead, but still. Is this what we want?

I've been thinking about making the same change to the iOS/macOS SDKs' public API for queryRenderedFeatures: accept an array of MGLStyleLayers instead of an array of identifiers in an awkwardly named parameter. The overhead is negligible for moderately complex styles. macosapp, for instance, retrieves and wraps every style layer on load, and any performance impact seems reasonable.

Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

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

Sweet – this will unblock plenty of use cases, plus some improvements I've been meaning to make around VoiceOver accessibility support.

In addition to the comments below, please add an entry to the iOS and macOS changelogs. Thanks!

@return An array of objects conforming to the `MGLFeature` protocol that
represent features in the sources used by the current style.
*/
- (NS_ARRAY_OF(id <MGLFeature>) *)featuresWithSourceIdentifier:(NSString *)sourceIdentifier sourceLayerIdentifier:(nullable NSString *)sourceLayerIdentifier predicate:(nullable NSPredicate *)predicate NS_SWIFT_NAME(features(sourceIdentifier:sourceLayerIdentifier:predicate:));
Copy link
Contributor

Choose a reason for hiding this comment

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

How about -featuresLoadedInSource:sourceLayerIdentifier:predicate:?

Especially if this method only works with a single source at a time, I think it would be desirable to require the developer to pass in an MGLSource object instead of a source identifier. That encourages the developer to obtain an existing source and helps to clarify the distinction between a source identifier and a source layer identifier.

Additionally, we probably want to emphasize that this method returns the features that have already loaded, not all the possible features in the source (which for a vector source could be quite unwieldy). This isn't quite a reverse geocoding API, after all.

Copy link
Contributor

Choose a reason for hiding this comment

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

-featuresLoadedBySource:sourceLayerIdentifier:predicate: would avoid overusing "in", especially if we later add a bounding box option.

Swift can probably come up with a decent name for this method without the NS_SWIFT_NAME attribute. (Try it out locally by trying to call the method in one of our Swift unit test files.) If not, try providing a Swift name of features(loadedBy:sourceLayerIdentifier:predicate:).

Copy link
Contributor

Choose a reason for hiding this comment

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

We'll want to push the change suggested above (moving the query method to the Source object itself) out to the SDK level, making this -[MGLSource featuresLoaded:inLayerWithIdentifier:predicate:] or similar.

Features come from tiled vector data or GeoJSON data that is converted to tiles
internally, so feature geometries are clipped at tile boundaries and features
may appear duplicated across tiles. For example, suppose the specified
rectangle intersects with a road that spans the screen. The resulting array
Copy link
Contributor

Choose a reason for hiding this comment

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

Some of this explanation is specific to -visibleFeaturesInRect:. This method by contrast accepts no rectangle parameter.

@ivovandongen ivovandongen force-pushed the 5792-query-source-features branch 2 times, most recently from 2b1e017 to 3f742a9 Compare March 8, 2017 06:03
Features come from tiled vector data or GeoJSON data that is converted to tiles
internally, so feature geometries are clipped at tile boundaries and features
may appear duplicated across tiles. For example, suppose the specified
rectangle intersects with a road that spans the screen. The resulting array
Copy link
Contributor

Choose a reason for hiding this comment

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

No rectangle is specified as part of this API.

- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NS_ARRAY_OF(NSString *) *)tileURLTemplates options:(nullable NS_DICTIONARY_OF(MGLTileSourceOption, id) *)options NS_DESIGNATED_INITIALIZER;

#pragma mark Querying the source's features
Copy link
Contributor

Choose a reason for hiding this comment

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

Since jazzy uses marks to generate section headings, it’s important to keep them consistent (title case, describes a task, uses the most commonplace terms possible). How about “Accessing a Source’s Content”, which is the same mark we use in MGLShapeSource?

@@ -210,6 +210,31 @@ MGL_EXPORT
*/
@property (nonatomic, copy, nullable) NSURL *URL;

#pragma mark Querying the source's features
Copy link
Contributor

Choose a reason for hiding this comment

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

I think -featuresMatchingPredicate: falls neatly under the existing “Accessing a Source’s Content” section heading.

@return An array of objects conforming to the `MGLFeature` protocol that
represent features in the sources used by the current style.
*/
- (NS_ARRAY_OF(id <MGLFeature>) *)featuresWithPredicate:(nullable NSPredicate *)predicate NS_SWIFT_NAME(features(predicate:));
Copy link
Contributor

Choose a reason for hiding this comment

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

Call this method -featuresMatchingPredicate: and remove the NS_SWIFT_NAME attribute. Swift will automatically bridge it as features(matching:).

The convention in Swift 3 is for argument labels to be prepositions or participles like “matching” instead of prepositional objects like “predicate”. matching: would be more intuitive argument label than with: in this case. (It’d also be consistent with methods like -[CNContactStore groupsMatchingPredicate:error:], -[EKEventStore eventsMatchingPredicate:], and -[XCUIElementQuery matchingPredicate:] in standard Apple libraries.)

current style and provides access to attributes specified by the relevant
<a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile sources</a>.
The returned array includes features specified in vector and GeoJSON tile
sources but does not include anything from raster, image, or video sources.
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that this method lives on MGLShapeSource, it’s unnecessary to point out that it won’t return features from non-shape sources. It’s also important to note that this method may return features that exist in the source but are not currently drawn by a style layer.

current style and provides access to attributes specified by the relevant
<a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">tile sources</a>.
The returned array includes features specified in vector and GeoJSON tile
sources but does not include anything from raster, image, or video sources.
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, there’s no need here to talk about other sources.

the road within each map tile is included individually.

@param sourceLayerIdentifiers The source layers to include in the query. Only the
features contained in this source layers are included in the returned array. At
Copy link
Contributor

Choose a reason for hiding this comment

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

s/this source layers/these source layers/

least one source layer is required.
@param predicate A predicate to filter the returned features.
@return An array of objects conforming to the `MGLFeature` protocol that
represent features in the sources used by the current style.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any significance to the order in which the features are returned? (It’s fine if there isn’t; we may even want to leave the order unmentioned as wiggle room for future improvements.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, not any order in particular and I don't think we should make any guarantees for now. Right now the order is sourceLayer, feature index.

#pragma mark Querying the source's features

/**
Returns an array of map features for this source, restricted to the given
Copy link
Contributor

Choose a reason for hiding this comment

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

s/for this source/loaded by this source/

XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style);

[_styleLoadingExpectation fulfill];
Copy link
Contributor

Choose a reason for hiding this comment

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

In case you’re wondering why this works for shape sources but not vector sources, -mapView:didFinishLoadingStyle: gets called as soon as we’re done loading and parsing the style itself. It isn’t guaranteed that all sources load by this point. If you wanted to query a vector source too, you’d need to wait until -mapViewDidFinishLoadingMap: is called. But this is perfectly fine for the inline GeoJSON sources in query-style.json, since they load as part of the style.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you wanted to query a vector source too, you’d need to wait until -mapViewDidFinishLoadingMap: is called

I've tried this, but this callback is never fired in this setup.

this is perfectly fine for the inline GeoJSON sources in query-style.json, since they load as part of the style.

I expected the same, but the inline geojson sources also don't seem to load this way.

@ivovandongen ivovandongen force-pushed the 5792-query-source-features branch 2 times, most recently from 4c1aadc to 4219e26 Compare March 8, 2017 23:29
@ivovandongen ivovandongen added Android Mapbox Maps SDK for Android iOS Mapbox Maps SDK for iOS labels Mar 8, 2017
@ivovandongen ivovandongen requested a review from tobrun March 8, 2017 23:29
@ivovandongen
Copy link
Contributor Author

@jfirebaugh Made the changes you requested and adapted the SDK bindings. Adding support for multiple sourceLayers resulted in some code duplication in: GeoJSONTile:: querySourceFeatures. Could extract the common functionality, not sure if it's worth it.

Copy link
Member

@tobrun tobrun left a comment

Choose a reason for hiding this comment

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

Tested the Android sample and getting 4 features when clicking the map. Android side of things looks good to 🚢

Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

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

A couple documentation nits, but the iOS/macOS changes are 👌 otherwise.


Each object in the returned array represents a feature for the
current style and provides access to attributes specified by the
<a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources">source</a>.
Copy link
Contributor

Choose a reason for hiding this comment

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

There’s also no need to link “source”, since this method lives inside an MGLSource subclass.

#pragma mark Accessing a Source’s Content

/**
Returns an array of map features for this source, restricted to the given
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: s/for this source/loaded by this source/

may appear duplicated across tiles.

@param sourceLayerIdentifiers The source layers to include in the query. Only the
features contained in this source layers are included in the returned array. At
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: s/this source layers/these source layers/

@ivovandongen ivovandongen removed the ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold label Mar 9, 2017
@ivovandongen ivovandongen merged commit b2b5f9e into master Mar 9, 2017
@ivovandongen ivovandongen deleted the 5792-query-source-features branch March 9, 2017 21:11
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Android Mapbox Maps SDK for Android Core The cross-platform C++ core, aka mbgl feature iOS Mapbox Maps SDK for iOS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants