Yandex Map v3 setPosition относительно попапа

Всем привет. У меня есть кастомный попап при клике на точку карты. Как можно сделать подскролл при клике на точку, что бы по центру был попап?

/** Yandex Map type - Feature to clusterize on a map */
type Feature = GenericPointFeature<LngLat>;

const openPopup = (feature: Feature, e: MouseEvent) => {
    mapMarkerActive.htmlElement = e.target as HTMLImageElement
    mapMarkerActive.htmlElement.src = '/images/map/marker_active.svg'

    map.setLocation({
        center: [
            Number(feature.properties?.longitude),
            Number(feature.properties?.latitude),
        ] as LngLat,
        duration: 500,
    })

    popup = new YMapMarker(
        {
            coordinates: feature.geometry.coordinates,
        },
        popupTemplate(feature)
    )
    map.addChild(popup)
}

Я вижу это так, что вот тут для latitude нужно вычесть высоту попапа, но как правильно вычесть из координат пиксели, да еще и в зависимости от зума на карте?

map.setLocation({
    center: [
        Number(feature.properties?.longitude),
        Number(feature.properties?.latitude),
    ] as LngLat,
    duration: 500,
})

Нашел в документации вот такие методы https://yandex.com/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/map.Converter Но как будто это немножно не то. Да и для 3.0 версии не нашел эти методы в документации.


Ответы (2 шт):

Автор решения: Dmitriy

В общем отчасти спас ChatGPT, но немного было немного нето по координатам. Методом подбора и тестов вывел вот такую функцию

/** Yandex Map type - Feature to clusterize on a map */
type Feature = GenericPointFeature<LngLat>;

const openPopup = (feature: Feature, e: MouseEvent) => {
    mapMarkerActive.htmlElement = e.target as HTMLImageElement
    mapMarkerActive.htmlElement.src = '/images/map/marker_active.svg'

    map.setLocation({
        center: [
            Number(feature.properties?.longitude),
            // 70 это как раз на сколько нужно сместить карту в пикселях относительно маркера/точки
            Number(feature.properties?.latitude) + yandexPixelsToLatitude(70, map.zoom),
        ] as LngLat,
        duration: 500,
    })

    popup = new YMapMarker(
        {
            coordinates: feature.geometry.coordinates,
        },
        popupTemplate(feature)
    )
    map.addChild(popup)
}

function yandexPixelsToLatitude(pixels, zoom) {
    // Размер тайла карты, обычно равен 256 пикселей.
    const tile_size = 256;
    
    // Масштаб, который изменяется в зависимости от уровня зума.
    const scale = 256 * Math.pow(2, zoom);
    
    // Радиус Земли в метрах.
    const earth_radius = 6378137;
    
    // Смещение начала координат для преобразования пикселей.
    const origin_shift = tile_size / 2.0;
    
    // Количество метров на пиксель, зависящее от масштаба.
    // earth_radius * Math.PI * 2 — это длина экватора Земли в метрах.
    // tile_size * scale — это количество пикселей на карте при данном масштабе.
    const metersPerPixel = (earth_radius * Math.PI * 2) / scale;

    // Вычисление координаты 'y' в метрах на основе пиксельного смещения от начала координат.
    // (origin_shift - pixels) — это смещение от начала координат в пикселях.
    const y = ((origin_shift - pixels) * metersPerPixel) / earth_radius;

    // Преобразование 'y' в радианы и вычисление широты.
    // Math.exp(y) — это экспоненциальная функция, которая нужна для обратного преобразования из метров в координаты.
    // Math.atan() — это арктангенс, который преобразует значение обратно в радианы.
    // Умножение на 360 / Math.PI - 90 преобразует радианы в градусы и сдвигает значение, чтобы получить широту.
    return ((Math.atan(Math.exp(y)) * 360) / Math.PI - 90)*500;
}

500 это уже мой коэффициент, которые мне дал верный результат.

→ Ссылка
Автор решения: nWacky

Если:

  • есть точка на карте, в которой установлена метка
  • есть popup, центр у которого = центр точки

Можно сделать popup по центу горизонтально через css
transform: translate(-50%, 20px)
(-50% по оси x, на 20px ниже от центра точки).

Теперь popup будет прямо под точкой.

При клике на точку осталось его сделать по центру.

Сделать popup по центру можно примерно как в centerPopup:

// как в `import type { LngLat } from "@yandex/ymaps3-types";`
type LngLat = [lng: number, lat: number];

type Point = {
  id: number;
  coordinates: LngLat;
};

/** Instance Яндекс Карты */
const map: YMap | null = null;

/**
 * Маркер яндекс карты, в котором показан popup.
 *
 * Центр popup и маркера должны быть в одной точке.
 */
const popupMarker: YMapMarker | null = null;

const centerPopup = () => {
  if (!map) {
    return;
  }

  const popup = popupMarker;
  if (!popup) {
    return;
  }

  // popup.element - <div class="__ymap-marker">
  const popupRectPx = popup.element?.getBoundingClientRect();

  /** Центр точки */
  const centerCoord: LngLat = popup.coordinates;

  /**
   * Размер карты в пикселях
   *
   * @example { x: 657, y: 500 }
   */
  const mapSizePx = m.size;

  /** bounds - точки границ карты LngLat */
  const [mapSizeCoordTopLeft, mapSizeCoordBottomRight] = m.bounds;

  /** Сколько longitude (ось x) показано на карте */
  const lngX = mapSizeCoordBottomRight[0] - mapSizeCoordTopLeft[0];
  /** Сколько latitude (ось y) показано на карте */
  const latY = mapSizeCoordTopLeft[1] - mapSizeCoordBottomRight[1];

  /** Сколько longitude в одном пикселе */
  const lngXPerPixel = lngX / mapSizePx.x;
  /** Сколько latitude в одном пикселе */
  const latYPerPixel = latY / mapSizePx.y;

  /** Центр карты (x) такой же */
  const centerLng = centerCoord[0];

  /** Половина высоты popup в единицах latitude */
  const popupHalfHeightLat = (popupRectPx.height / 2) * latYPerPixel;

  /**
   * Центр карты (y) = центр точки - 50% popup.
   *
   * 50% popup будет над новым центром, 50% под.
   */
  const centerLat = centerCoord[1] - popupHalfHeightLat;

  map.setLocation({
    center: [centerLng, centerLat],
    duration: 300,
  });
};
→ Ссылка