Собственные реализации слоев
Важно.
Собственная реализации слоев это экспериментальная опция. Для ее включения напишите нам.
Помимо встроенных слоев, JS API повзоляет создавать собственные реализации для векторных и растровых слоев.
В них вы можете полностью взять на себя отображение слоя и кастомизировать таким образом карту, как угодно.
Важно.
Векторные и растровые слои не взаимозаменяемы. У них разные сигнатуры конструкторов и разный принцип работы.
В общем случае собственная имплементация задается таким образом:
const {YMap, YMapLayer} = ymaps3;
const map = new YMap(document.getElementById('app'), {location: LOCATION});
map.addChild(
new YMapLayer({
id: 'custom-layer-id',
type: 'custom',
zIndex: 10000, // Позволяет определить порядок слоя
/**
* Позволяет определить сортировку слоев, в группе слоя с id=`${ymaps3.YMapDefaultSchemeLayer.defaultProps.source}:buildings`
* Подробнее о порядке слоев читайте ниже
*/
grouppedWith: `${ymaps3.YMapDefaultSchemeLayer.defaultProps.source}:buildings`,
implementation: ({source, type, effectiveMode}) => {
/**
* В зависимости от комбинаций mode(raster или vector), source: string и type: string вы можете,
* как полностью переопределить поведение стандартных слоев, так и объявить свои реализации
*/
if (effectiveMode === 'raster') {
if (type === 'markers') {
return class {
constructor({}) {}
};
}
return class {
constructor({}) {}
};
}
if (effectiveMode === 'vector') {
return class {
constructor({}) {}
};
}
}
})
);
Подробнее про порядок слоев
Важно.
Обратите внимание, implementation
возвращает конструктор, а не его экземпляр.
Для соотвествующих mode
implementation должен вернуть конструктор класса реализующий тип:
- raster - RasterLayerImplementationConstructor
- vector - VectorLayerImplementationConstructor
Если implementation
не задан, или не вернул конструктор, то используются встроенные в JS API реализации.
Растровые пользовательские слои
Конструктор кастомного растрового слоя, должен реализовать интерфейс RasterLayerImplementationConstructor
import type {PixelCoordinates, Camera, WorldOptions, Projection} from '@yandex/ymaps3-types/common/types';
class CustomLayer {
private element: HTMLElement;
private size: PixelCoordinates;
private camera: Camera;
private worldOptions: WorldOptions;
private projection: Projection;
constructor(options) {
this.requestRender = options.requestRender;
this.element = options.options;
this.size = options.size;
this.camera = options.camera;
this.worldOptions = options.worldOptions;
this.projection = options.projection;
}
destroy(): void {
// clean or smth
}
render(props): void {
// Render data
this.element.innerText = new Date.toString();
this.requestRender();
}
}
map.addChild(
new YMapLayer({
// ...
implementation: ({source, type, effectiveMode}) => {
if (type === 'custom' && effectiveMode === 'vector') {
return CustomLayer;
}
}
})
);
Реализация добавляет в карту слой в котором вы полностью контролируете содержимое HTML элемента.
В нем можно отобразить любое содержимое: HTMLCanvasElement
, SVG
, React` приложение и т.д.
В примерах, вы найдете реализацию снега на Canvas
Поиск объектов под курсором
Реализация слоя может поддерживать поиск объектов под курсором. Для этого необходимо реализовать метод findObjectInPosition
:
import {YMapHotspot} from '@yandex/ymaps3-types';
interface CustomLayer {
findObjectInPosition({
worldCoordinates,
screenCoordinates
}: {
worldCoordinates: WorldCoordinates;
screenCoordinates: PixelCoordinates;
}): YMapHotspot | null;
}
К примеру:
import type {
PixelCoordinates,
RasterLayerImplementationConstructorProps,
RasterLayerImplementationRenderProps,
WorldCoordinates
} from '@yandex/ymaps3-types';
export class CustomSVGLayer {
private __elm: HTMLElement;
constructor({projection, element}: RasterLayerImplementationConstructorProps) {
this.__elm = element;
// ...
}
findObjectInPosition({
worldCoordinates,
screenCoordinates
}: {
worldCoordinates: WorldCoordinates;
screenCoordinates: PixelCoordinates;
}) {
// Поиск элемента под курсором
const elm = document.elementFromPoint(screenCoordinates.x, screenCoordinates.y);
// Проверяем, что элемент находится внутри нашего слоя
if (elm && this.__elm.contains(elm) && elm.id) {
return new ymaps3.YMapHotspot(
{
type: 'Point',
coordinates: this.__projection.fromWorldCoordinates(worldCoordinates)
},
{
id: elm.id,
category: elm.getAttribute('class')
}
);
}
return null;
}
render({camera}: RasterLayerImplementationRenderProps) {
// ...
}
destroy() {
// ...
}
}
Тогда в YMapListener
можно будет "поймать" этот объект:
map.addChild(
new ymaps3.YMapListener({
onMouseEnter(entity) {
if (entity instanceof ymaps3.YMapHotspot) {
// К примеру меняем класс элемента
document.getElementById(entity.id).classList.toggle('hovered', true);
}
}
})
);
Векторные кастомные слои
Так как векторный движок оперирует 3d графикой, то и ваша реализация должна уметь использовать WebGLRenderingContext
для отрисовки.
Конструктор кастомного векторного слоя, должен реализовать интерфейс VectorLayerImplementationConstructor
abstract class Custom3dLayer {
constructor(
gl: WebGLRenderingContext,
options: {
requestRender: () => void;
}
) {
this.gl = gl;
this.options = options;
}
abstract render(arg: {
size: {
width: number;
height: number;
};
worlds: {
lookAt: Vec2;
viewProjMatrix: Matrix4;
}[];
}): {
color: WebGLTexture;
depth?: WebGLTexture;
};
destroy(): void {}
}
map.addChild(
new YMapLayer({
// ...
implementation: ({source, type, effectiveMode}) => {
if (effectiveMode === 'vector') {
return Custom3dLayer;
}
}
})
);
Задача сводится к реализации метода render
, который должен вернуть две WebGLTexture
текстуры.
Вы можете использовать для этого любой доступный вам WebGL
фреймворк.
В примерах вы найдете реализацию на ThreeJS
Порядок слоев
По умолчанию слои сортируются в порядке добавления, но этот порядок никак не гарантируется в React
или при использовании вложенности.
function App() {
const {showLayer2, toggleLayer2} = useState(false);
return (
<>
<button onClick={() => toggleLayer2(true)}>Show layer2</button>
<YMap>
<YMapLayer id={'layer1'} />
{showLayer2 && <YMapLayer id={'layer2'} />}
<YMapLayer id={'layer3'} />
</YMap>
</>
);
}
В дереве JSX элементы рсположены в порядке layer1, layer2, layer3. Но так как layer2
будет добавлен позднее, то в JS API порядок слоев будет таким:
layer1
layer3
layer2
Чтобы исправить ситуацию, каждому слою можно прописать настройку zIndex
function App() {
const {showLayer2, toggleLayer2} = useState(false);
return (
<>
<button onClick={() => toggleLayer2(true)}>Show layer2</button>
<YMap>
<YMapLayer zIndex={1000} id={'layer1'} />
{showLayer2 && <YMapLayer zIndex={1001} id={'layer2'} />}
<YMapLayer zIndex={1002} id={'layer3'} />
</YMap>
</>
);
}
Тогда в каком бы порядке вы не добавляли слои, они всегда остануться в таком расположении:
layer1
layer2
layer3
Иногда, слой необходимо расположить прямо под определенным встроенным слоем.
К примеру, при отображении 3d модели, ее необходимо рисовать сразу за слоем зданий (В реальности они рисуются одновременно, но это тема другой статьи).
Чтобы добится такой сортировки, ипользуется опция grouppedWith
:
function App() {
return (
<YMap>
<YMapLayer zIndex={1000} id={'layer1'} />
<YMapLayer zIndex={1001} id={'layer2'} />
<YMapLayer zIndex={1002} id={'layer3'} />
<YMapLayer zIndex={1003} grouppedWith={'layer2'} id={'layer4'} />
<YMapLayer zIndex={1004} grouppedWith={'layer2'} id={'layer5'} />
<YMapLayer zIndex={1000} grouppedWith={'layer2'} id={'layer6'} />
</YMap>
);
}
Слой layer6
будет сортирован в группе layer2/layer4/layer5/layer6
по полю zIndex.
Порядок слоев в током случае, будет:
layer1
layer6
layer2
layer4
layer5
layer3