maps icon indicating copy to clipboard operation
maps copied to clipboard

[Android] - Query SymbolLayer using `queryRenderedFeaturesInRect` just received an empty `FeatureCollection`

Open jun-nguyen-goldenowl opened this issue 5 years ago • 20 comments

Describe the bug I was trying to find drop-pins wrapped in a certain bounding box, in specified layerIds and null filter using MapView method queryRenderedFeaturesInRect. Each drop-pin is implemented with the combination of ShapeSource & SymbolLayer. It worked fine on iOS, even iOS 14 but on Android, it sucked. The result only contains an empty array typed FeatureCollection. However, method queryRenderedFeaturesInRect works with LineLayer both on Android and iOS.

To Reproduce

Below is the implementation for handling selecting a Marker which is obscured by MapboxGL.UserLocation

Example:

// Marker
import React from 'react';
import {
  MapView,
  ShapeSource,
  SymbolLayer,
  Camera,
} from '@react-native-mapbox-gl/maps';
import {
  Platform,
  NativeModules,
  NativeEventEmitter,
} from 'react-native';

const Marker = ({ id, onPress, coordinate, properties = {} }) => (
  <ShapeSource
    onPress={onPress}
    id={id}
    shape={{
      type: 'Feature',
      properties,
      geometry: {
        type: 'Point',
        coordinates: coordinate,
      },
    }}
  >
    <SymbolLayer
      id={`${id}-symbol`}
      style={{
        iconImage: 'https://s1.imghub.io/uzvBS.png',
        iconAnchor: 'bottom',
        iconAllowOverlap: true,
      }}
    />
  </ShapeSource>
)

// MapView
// -- get current user location (iOS)
const MyLocationDataManagerEmmiter = new NativeEventEmitter(NativeModules.MyLocationDataManager)
const getLocationManager = () => new Promise((resolve) => {
  const list = MyLocationDataManagerEmmiter.addListener(
    'significantLocationChange',
    (data) => {
      const { longitude, latitude } = data.location.coords
      resolve({
        longitude,
        latitude,
      })
      list.remove()
    },
    (err) => console.log('err: ', err),
  )
})
// -- get current user location (Android)
let _userLocationAndroid = null;

const handleUserLocationChange = (e) => {
  if (e && Platform.OS === 'android' ) {
    _userLocationAndroid = e.coords
  }
}

// -- get bounding box for point{x, y}
export const calcBoundingBox = (point, boxEdgeHalfSize) => {
  const nX = Number(point.x);
  const nY = Number(point.y);
  const halfSize = Number(boxEdgeHalfSize);
  return [
    nY - halfSize, // top
    nX + halfSize, // right
    nY + halfSize, // bottom
    nX - halfSize, // left
  ]
}

<MapboxGL.MapView
    ref={(ref) => { _map = ref || _map }}
>
    <MapboxGL.Camera
        ref={(ref) => {
          if (ref) {
            _camera = ref
          }
        }}
        zoomLevel={7}
        minZoomLevel={0}
        maxZoomLevel={18}
        followZoomLevel={18}
    />
    <MapboxGL.UserLocation
        showsUserHeadingIndicator
        onUpdate={handleUserLocationChange}
        onPress={async () => {
          let lng
          let lat
          if (Platform.OS === 'ios') {
            const { longitude, latitude } = await getLocationManager()
            lng = longitude
            lat = latitude
          } else {
            if (!_userLocationAndroid) return
            const { latitude, longitude } = _userLocationAndroid
            lng = longitude
            lat = latitude
          }
          if (Number.isNaN(lng) || Number.isNaN(lat)) {
            return
          }
          const point = await _map.getPointInView([lng, lat])
          console.log('_DEBUG_: point', point)

          const bbox = calcBoundingBox({ x: point[0], y: point[1] }, 10)
          console.log('_DEBUG_: bbox', bbox)

          const layerIds = pins.map((pin) => `${pin.identifier}-symbol`)
          console.log('_DEBUG_: layerIds', layerIds)

          const featureCollections = await _map.queryRenderedFeaturesInRect(bbox, null, layerIds)
          console.log('_DEBUG_: featureCollections', JSON.stringify(featureCollections, null, 2))
        }}
    />
</MapboxGL.MapView>

Expected behavior

  • The variable featureCollections should be exactly the same on both Android & iOS.
  • We can use queryRenderedFeaturesInRect to find all SymbolLayer in provided bounding box.

Screenshots Result after clicking on UserLocation

iOS image

Android image

Versions (please complete the following information):

  • Platform: [Android, iOS]
  • Device: [Pixel 3a. iPhone 8]
  • Emulator/ Simulator: [yes]
  • OS: [Android 10]
  • react-native-mapbox-gl Version [8.1.0-rc.5]
  • React Native Version [0.63.3]

jun-nguyen-goldenowl avatar Oct 19 '20 17:10 jun-nguyen-goldenowl

I'm getting a similar issue with queryRenderedFeaturesAtPoint on Android. Code works fine on iOS, but doesn't work on Android:

  const onPress = useCallback(
    async (feature: Feature) => {
      if (__DEV__) {
        console.log(feature);
      }
      if (feature.geometry.type !== 'Point') {
        return;
      }

      const point = await mapRef.current?.getPointInView(
        feature.geometry.coordinates,
      );

      console.log(point);

      if (!point) {
        return;
      }

      const featureQuery = await mapRef.current?.queryRenderedFeaturesAtPoint(
        point,
      );

      console.log(featureQuery);

      if (!featureQuery) {
        return;
      }

      const tappedFeature = featureQuery.features.filter(
        (f): f is Feature => f.id && f.properties?.name,
      )[0];

      if (tappedFeature) {
        onSelectFeature?.(tappedFeature);
      }
    },
    [onSelectFeature],
  );

The logs are interesting, because if I do this exactly the same side-by-side in an iOS simulator and an Android emulator, these are the logs:

iOS:     {"geometry": {"coordinates": [-0.1278433227608673, 51.50767608808047], "type": "Point"}, "properties": {"screenPointX": 199, "screenPointY": 431}, "type": "Feature"}
iOS:     [198.9999997594527, 430.99999959695907]
iOS:     {"features": [{"geometry": [Object], "id": 957821391, "properties": [Object], "type": "Feature"}], "type": "FeatureCollection"}
Android: {"geometry": {"coordinates": [-0.12793794265280667, 51.507698148552066], "type": "Point"}, "properties": {"screenPointX": 513.96240234375, "screenPointY": 850.95703125}, "type": "Feature"}
Android: [195.79519653320312, 324.1741027832031]
Android: {"features": [], "type": "FeatureCollection"}

On iOS getPointInView returns virtually the same numbers as the feature's screenPoint[X/Y] values, whereas on Android they are quite different. Could that be the issue?

Is #639 related to this? Also, getting this issue both on @react-native-mapbox-gl/[email protected] and rc.7

lukeramsden avatar Oct 20 '20 08:10 lukeramsden

@jun-nguyen-goldenowl, can you please create an example within the /example app. Preferably in the BugReportTemplate - make sure it runs afterwards.

Less time we spend setting up and debugging code samples is more time fixing the issue.

Thanks in advance 🙇

ferdicus avatar Oct 23 '20 14:10 ferdicus

I can also confirm that the following snippet was necessary to make queryRenderedFeaturesAtPoint work with the result of getPointInView:

    if (Platform.OS == 'android') {
      point[0] *= PixelRatio.get();
      point[1] *= PixelRatio.get();
    }

Would it perhaps make sense to add a method queryRenderedFeaturesAtLocation that handles this internally? (In addition to fixing the underlying bug with getPointInView).

savv avatar Nov 02 '20 21:11 savv

PRs appreciated :)

ferdicus avatar Nov 13 '20 16:11 ferdicus

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 17 '21 21:01 stale[bot]

Should we keep this open, even if nobody has had the time to fix it so far?

savv avatar Jan 18 '21 11:01 savv

Should we keep this open, even if nobody has had the time to fix it so far?

Definitely, I'll at an appropriate label to the ticket. Maybe someone stumbles upon it and actually has time :D

ferdicus avatar Jan 18 '21 12:01 ferdicus

@ferdicus could it be that fixing this bug is as simple as removing these two lines here? https://github.com/react-native-mapbox-gl/maps/blob/963a77b7ce716d1f5b8b64fd5a45c0fcf90aac61/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.java#L922

I couldn't see any similar logic on the iOS counterpart here: https://github.com/react-native-mapbox-gl/maps/blob/9103f2db5a5600f6a29db1644b2a09e5979da58d/ios/RCTMGL/RCTMGLMapViewManager.m#L116

It could be that iOS works in device pixels, but it doesn't seem so.

savv avatar Jan 18 '21 14:01 savv

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Mar 20 '21 00:03 stale[bot]

not stale

hannojg avatar Mar 29 '21 10:03 hannojg

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 02 '21 16:06 stale[bot]

Not stale

jun-nguyen-goldenowl avatar Jun 04 '21 01:06 jun-nguyen-goldenowl

@ferdicus could it be that fixing this bug is as simple as removing these two lines here?

https://github.com/react-native-mapbox-gl/maps/blob/963a77b7ce716d1f5b8b64fd5a45c0fcf90aac61/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.java#L922

I couldn't see any similar logic on the iOS counterpart here:

https://github.com/react-native-mapbox-gl/maps/blob/9103f2db5a5600f6a29db1644b2a09e5979da58d/ios/RCTMGL/RCTMGLMapViewManager.m#L116

It could be that iOS works in device pixels, but it doesn't seem so.

@savv , not sure - you can check the PR for the reasoning behind this addition: https://github.com/react-native-mapbox-gl/maps/pull/639

Maybe that'll give a hint

ferdicus avatar Jun 04 '21 15:06 ferdicus

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 10 '21 03:08 stale[bot]

Don't you dare stale bot

hannojg avatar Aug 10 '21 06:08 hannojg

Any update or workaround for this? Still experiencing this issue with release 8.4.0 after migrating away from the deprecated library: https://github.com/nitaliano/react-native-mapbox-gl where it used to work consistently for both platforms.

getPointInView([ 4.4024642, 51.2194475 ]) on iOS returns [207, 447.999] while getPointInView() on Android returns [196.5, 391]. This prevents me from selecting the right geometry on Android. Tested on multiple Android and iOS devices.

(EDIT: Moved images in table for better viewing [ferdicus])

iOS Android
screenshot_iphone_simulator screenshot_android_emulator

parkdcom avatar Nov 09 '21 20:11 parkdcom

Any update or workaround for this?

See here

https://github.com/react-native-mapbox-gl/maps/issues/1085#issuecomment-720729641

savv avatar Nov 10 '21 13:11 savv

Any update or workaround for this?

See here

#1085 (comment)

My bad totally missed that part, just tried it out and the fix is working like a charm! Thanks a lot @savv !

parkdcom avatar Nov 10 '21 15:11 parkdcom

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 18 '22 18:04 stale[bot]

Not stale

savv avatar Apr 18 '22 19:04 savv

Pls try with current v10 releases and open a new issue if it's still happens there

mfazekas avatar Mar 14 '23 15:03 mfazekas