Как преобразовать широту/долготу в правильное расположение пикселей в плитке GridLayer

Я играю с созданием GridLayer на основе Konva для Leaflet (в основном абстракция вокруг элементов холста, чтобы попытаться эффективно отобразить десятки тысяч функций). У меня есть некоторый код, который, кажется, работает до некоторой степени (строки в моих примерах данных, похоже, совпадают с тем, что я ожидал), но я получаю странное поведение. В частности, функции будут заметно «телепортироваться» или полностью исчезать. Кроме того, нередко можно увидеть разрывы линий по краям плитки. Я подозреваю, что это означает, что я неправильно вычисляю местоположение пикселя внутри каждой плитки (хотя, безусловно, возможно, что-то еще не так). Я в основном идентифицирую местоположение пикселя плитки (x, y в renderStage()) и перевожу положение пикселя карты на это количество пикселей (pt.x и pt.y, сгенерированные путем проецирования широты/долготы). Это предназначено для создания массива [x1, y1, x2, y2, ...], который можно отобразить в отдельной плитке. Ожидается, что все будет в формате EPSG:4326.

Кто-нибудь знает, как правильно проецировать широту/долготу в пиксельные координаты в отдельных плитках GridLayer? Существует множество примеров того, как сделать это для всей карты, но, похоже, это не совсем понятно, как найти те же местоположения пикселей в тайлах, смещенных от верхнего левого угла карты.

import { GridLayer, withLeaflet } from "react-leaflet";
import { GridLayer as LeafletGridLayer } from "leaflet";
import { Stage, Line, FastLayer } from "konva";
import * as Util from 'leaflet/src/core/Util';
import _ from "lodash";

export const CollectionLayer = LeafletGridLayer.extend({
  options: {
    tileSize: 256
  },
  initialize: function(collection, props) {
    Util.setOptions(this, props)
    this.collection = collection;
    this.stages = new Map();
    this.shapes = {};
    this.cached = {};
    this.on('tileunload', (e) => {
      const stage = this.stages[e.coords]
      if (stage) {
        this.stages.delete(e.coords)
        stage.destroy()
      }
    })
  },
  renderStage: function(stage, coords, tileBounds) {
    const x = coords.x * this._tileSize.x
    const y = coords.y * this._tileSize.y
    const z = coords.z;
    const layer = stage.getLayers()[0]

    if (!layer || !tileBounds) return;
    _.each(this.collection.data, (entity, id) => {
      if (entity.bounds && tileBounds.intersects(entity.bounds)) {
        let shape = this.shapes[id]
        if (!shape) {
          shape = new Line()
          shape.shadowForStrokeEnabled(false)
          this.shapes[id] = shape
        }
        layer.add(shape);
        const points = entity.position.reduce((pts, p) => {
          const pt = this._map.project([p.value[1], p.value[0]], this._tileZoom)
          pts.push(pt.x - x);
          pts.push(pt.y - y);
          return pts
        }, [])
        shape.points(points);
        shape.stroke('red');
        shape.strokeWidth(2);
        this.shapes[id] = shape
      }
    })
    layer.batchDraw()
  },
  createTile: function(coords) {
    const tile = document.createElement("div");
    const tileSize = this.getTileSize();
    const stage = new Stage({
      container: tile,
      width: tileSize.x,
      height: tileSize.y
    });
    const bounds = this._tileCoordsToBounds(coords);
    const layer = new FastLayer();
    stage.add(layer);
    this.stages[coords] = stage
    this.renderStage(stage, coords, bounds);
    return tile;
  }
});

class ReactCollectionLayer extends GridLayer {
  createLeafletElement(props) {
    console.log("PROPS", props);
    return new CollectionLayer(props.collection.data, this.getOptions(props));
  }
  updateLeafletElement(fromProps, toProps) {
    super.updateLeafletElement(fromProps, toProps);
    if (this.leafletElement.collection !== toProps.collection) {
      this.leafletElement.collection = toProps.collection
      this.leafletElement.redraw();
    }
  }
}

export default withLeaflet(ReactCollectionLayer);

person rfestag    schedule 17.08.2018    source источник


Ответы (1)


Ожидается, что все будет в формате EPSG:4326.

No.

Когда вы работаете с растровыми данными (фрагментами изображений), все должно быть либо в формате CRS отображения карты, который (по умолчанию) EPSG:3857, либо в пикселях относительно начала координат CRS. Эти концепции объясняются более подробно в одном из руководств по Leaflet.

На самом деле, вы, кажется, работаете в пикселях здесь, по крайней мере, для ваших очков:

const pt = this._map.project([p.value[1], p.value[0]], this._tileZoom)

Однако ваш расчет смещения пикселя для каждой плитки слишком наивен:

const x = coords.x * this._tileSize.x
const y = coords.y * this._tileSize.y

Вместо этого следует полагаться на метод private _getTiledPixelBounds из L.GridLayer, например:

const tilePixelBounds = this._getTiledPixelBounds();
const x = tilePixelBounds.min.x;
const y = tilePixelBounds.min.y;

И используйте эти границы, чтобы добавить некоторые проверки работоспособности при циклическом просмотре точек:

const pt = this._map.project([p.value[1], p.value[0]], this._tileZoom);
if (!tilePixelBounds.contains(pt)) { console.error(....); }

С другой стороны:

[...] абстракция вокруг элементов холста, чтобы попытаться эффективно отобразить десятки тысяч функций

Я не думаю, что использование Konva для фактического рисования элементов на <canvas> улучшит производительность — методы точно такие же, как и в Leaflet (и, если мы говорим о разбиении векторных данных, те же, что и в Leaflet.VectorGrid ). Десять тысяч вызовов функций рисования холста будут занимать одно и то же время, независимо от того, какая библиотека находится сверху. Если у вас есть время рассмотреть другие альтернативы, Leaflet.GLMarkers и его рендеринг WebGL могут обеспечить лучшую производительность. за счет меньшей совместимости и более высоких затрат на интеграцию.

person IvanSanchez    schedule 20.08.2018
comment
Извините, я не выразился яснее, когда я сказал, что все должно быть EPSG:4326, я имел в виду, что у меня была настроена карта для использования этого, и фактические данные, которые я отображаю, используют это (мой код здесь предназначен для преобразования их в пикселей для рендеринга). Я попробовал ваш код для вычисления x и столкнулся с двумя проблемами. 1) Кажется, требуется передать центральную широту (я использовал this._map.getCenter()). Без него я получаю, что latlng не определен, пока он пытается спроецировать его в этом методе. 2) Когда я прохожу мимо центра карты, визуализированные данные находятся повсюду, а не там, где я их ожидал, при этом большинство плиток пусты. - person rfestag; 21.08.2018