Omar Abdelwahed
Omar Abdelwahed

Reputation: 93

Using NSPredicate to filter MGLFeature $id's in Mapbox MGLFillExtrusionStyleLayer

I'm using Mapbox's iOS SDK. I have 2 MGLFillExtrusionStyleLayer layers showing extruded buildings as provided in the iOS example projects. After setup (didFinishLoadingStyle:) I'm trying to update the style of the second layer to show buildings around the center point in red. I'm using [mapview visibleFeaturesInRect: inStyleLayersWithIdentifiers:] to retrieve MGLFeature's in a 20x20 CGRect around the center point of the mapview. As a check, I pass the array of MGLFeature's to [mapview addAnnotations:]. I then create a NSPredicate to filter the $id's of these MGLFeatures against the layer. As an example, my predicate is of the form: $id IN {27, 19, 16, 16, 15, 15, 15}. (You'll note some identifiers are repeated in the return array of MGLFeature's.) I then assign this predicate to my layer: layer.predicate = newPredicate.

Red buildings should be the same as the blue annotations.

If you look at the attached screenshot, you'll see the blue annotations under the center point of the mapview indicate the MGLFeature's array is correct. However, my layer is coloring more than just the immediate building in red.

Code snippet where I filter the layer (inside a loop iterating an array of layers):

    // get fill extrusion layer
    MGLFillExtrusionStyleLayer *layer = (MGLFillExtrusionStyleLayer*)[_buildingLayers objectAtIndex:i];

    // we are in a loop so get the correct layer id
    NSSet *set = [NSSet setWithObject:[NSString stringWithFormat:@"3d-buildings-%d", i]];

    // return features in a 20x20 CGRect around center point of mapview
    NSArray *features = [_mapView visibleFeaturesInRect:boundingBox inStyleLayersWithIdentifiers:set];

    // sanity check that MGLFeature's array is correct.
    // shows in Blue on screenshot
    [_mapView addAnnotations:features];

    // create string filter of MGLFeature $id's: $id IN {1,2,3,4 ...}
    if ([features count] > 0) {
        NSMutableString *filter = [NSMutableString stringWithString:@"%K IN "];
        for (int j = 0; j < [features count]; j++) {
            MGLPolygonFeature *feature = [features objectAtIndex:j];
            NSLog(@"[%@] %@", feature.identifier, [feature valueForKey:@"attributes"]);
            if (j == 0) {
                [filter appendString:[NSString stringWithFormat:@"{%@", feature.identifier]];
            } else {
                [filter appendString:[NSString stringWithFormat:@", %@", feature.identifier]];
            }
        }

        // filter string now of form: %K IN {27, 19, 16, 16, 15, 15, 15}
        [filter appendString:@"}"];

        // change buildings to red 
        // (underlying layer has same buildings in gray)
        layer.fillExtrusionColor = [MGLStyleValue valueWithRawValue:[UIColor redColor]];

        // substitute $id for %K
        NSPredicate *pred = [NSPredicate predicateWithFormat:filter argumentArray:[NSArray arrayWithObject:@"$id"]];

        // log.. now predicate is of form (example): $id IN {27, 19, 16, 16, 15, 15, 15}
        NSLog(@"pred: %@", pred);

        // assign to layer
        layer.predicate = pred;
    }

My immediate questions: Are MGLFeature $id's not unique? Am I using predicates and layers incorrectly?

Thank you!

Upvotes: 1

Views: 676

Answers (1)

Omar Abdelwahed
Omar Abdelwahed

Reputation: 93

I was able to get a response from Mapbox directly. To my second question, I am indeed using predicates and layers correctly, However, to my first question, "Are MGLFeature $id's not unique?" It turns out they are not unique. See Mapbox's reply, below...

Feature IDs are not unique because our source data for buildings does not contain the same unique identifiers from OpenStreetMap in order to make our vector tiles more performant. The duplicate feature IDs you're seeing are likely coming from features that stretch across more than one vector tile as well. If you would like to highlight individual buildings, you will need to add your own custom data containing buildings that have a unique identifier. This is similarly shown in one of our older examples found here: https://blog.mapbox.com/visualizing-an-entire-citys-buildings-live-with-runtime-styling-453fe7e39ae6

For those who are interested, I did manage to create a workaround where I created a new MGLShapeSource with the returned array of MGLFeature's I retrieve within the 20x20 CGRect. I add this new source to my map's MLGStyle and then create and add a whole new MGLFillExtrusionStyleLayer to the map with that MGLShapeShource. I set the new layer's fillExtrusionColor to red to visually differentiate the found buildings over the rest of my layers. Works well.

Snippet:

MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"filteredSource" features:MyArrayOfMGLFeatures options:nil];
[_mapView.style addSource:source];
MGLFillExtrusionStyleLayer *layer = [[MGLFillExtrusionStyleLayer alloc] initWithIdentifier:@"redBuildings" source:source]; 
layer.fillExtrusionColor = [MGLStyleValue valueWithRawValue:[UIColor redColor];
[_mapView.style addLayer:layer];

Upvotes: 2

Related Questions