0

I need custom tooltip with semitransparent background which overlays the map.

So in common we draw first MapView. After marker press on top of MapView we draw overlay (backgroundColor: "#00000033"), on top of it draw tooltip and image over the marker position to simulate highlight. And now I need to get absolute position of marker on screen to draw my image over it. There is point prop in onPress nativeEvent regarding to docs but I haven't it in my real event and I don't know if that is what I need.

{
   "action": "marker-press",
   "coordinate": {
      "latitude": -15.3469687,
      "longitude": 37.100195
   },
   "id": "unknown",
   "target": 295
}

Is there a way to get marker's on screen position?

Asgu
  • 712
  • 5
  • 15

2 Answers2

0

Yes, you can use the measureInWindow method on the ref of the custom component. If your custom component doesn't have this method available, you can wrap it in a View, and use the method on that.

Example:

const markerRef = useRef();
const [markerPosition, setMarkerPosition] = useState({});

const measureMarker = () => {
  markerRef.current?.measureInWindow((x, y, width, height) => {
    const { width: screenWidth, height: screenHeight } = Dimensions.get('screen');
    // if any non-zero, on-screen values, save measurements
    if (!(x || y || width || height)) return;
    if (x > screenWidth || y > screenHeight) return;
    setMarkerPosition({x, y, width, height});
  }
};

...

return (
  <View ref={markerRef} onLayout={measureMarker}>
    <CustomMarker
      ...
);

See the docs for measureInWindow for more. measure would also work for the purpose of getting the on-screen position.

Note that these callbacks won't work until native layout is completed.

Abe
  • 4,500
  • 2
  • 11
  • 25
0

I came back to this problem after a year and what I did. First of all this problem is related only to iOS, on Android position property is presented and valid. Next my code:

const ratio = PixelRatio.get();

const {northEast, southWest} = await mapRef.current?.getMapBoundaries();

const longitudeDelta = northEast.longitude - southWest.longitude;

mapContainerRef.current?.measureInWindow((x, y, w, h) => {
  const coord = convertGeoToPixel(
    coordinate.latitude,
    coordinate.longitude,
    w,
    h,
    southWest.longitude,
    longitudeDelta,
    southWest.latitude,
    (southWest.latitude * Math.PI) / 180,
  );

  setCoords({
    left: coord.x * ratio,
    top: coord.y * ratio,
  });
});

Where convertGeoToPixel is a function from this answer about Mercator projection https://stackoverflow.com/a/27313080

function convertGeoToPixel(
  latitude,
  longitude,
  mapWidth, // in pixels
  mapHeight, // in pixels
  mapLonLeft, // in degrees
  mapLonDelta, // in degrees (mapLonRight - mapLonLeft);
  mapLatBottom, // in degrees
  mapLatBottomDegree,
) {
  // in Radians
  var x = (longitude - mapLonLeft) * (mapWidth / mapLonDelta);

  latitude = (latitude * Math.PI) / 180;
  var worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
  var mapOffsetY =
    (worldMapWidth / 2) *
    Math.log(
      (1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree)),
    );
  var y =
    mapHeight -
    ((worldMapWidth / 2) *
      Math.log((1 + Math.sin(latitude)) / (1 - Math.sin(latitude))) -
      mapOffsetY);

  return {x: x, y: y};
}

So convertGeoToPixel returns coordinates of the marker relative to the map view

Asgu
  • 712
  • 5
  • 15