diff --git a/demo/index.html b/demo/index.html
index 57e05db..abf7e69 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -6,25 +6,12 @@
diff --git a/demo/index.ts b/demo/index.ts
index c68c381..76fc17a 100644
--- a/demo/index.ts
+++ b/demo/index.ts
@@ -10,7 +10,7 @@ async function start() {
const map = new mapglAPI.Map('container', {
center: [47.245286302641034, 56.134743473834099],
- zoom: 17.9,
+ zoom: 18.9,
key: 'cb20c5bf-34d3-4f0e-9b2b-33e9b8edb57f',
pitch: 45,
rotation: 330,
@@ -20,7 +20,7 @@ async function start() {
(window as any).map = map;
const plugin = new GltfPlugin(map, {
- modelsLoadStrategy: 'dontWaitAll',
+ modelsLoadStrategy: 'waitAll',
modelsBaseUrl: 'https://disk.2gis.com/digital-twin/models_s3/realty_ads/zgktechnology/',
floorsControl: { position: 'centerRight' },
poiConfig: {
@@ -34,7 +34,7 @@ async function start() {
hoverHighlight: {
intencity: 0.1,
},
- groundCoveringColor: 'rgba(233, 232, 220, 0.8)',
+ groundCoveringColor: 'rgba(0, 0, 0, 0.8)',
});
(window as any).gltfPlugin = plugin;
@@ -46,7 +46,7 @@ async function start() {
.getContainer()
.addEventListener('click', () => {
plugin.removeRealtyScene();
- plugin.addRealtyScene(REALTY_SCENE, { modelId: '03a234cb', floorId: '235034' });
+ plugin.addRealtyScene(REALTY_SCENE);
});
new mapglAPI.Control(map, '', {
@@ -63,7 +63,9 @@ async function start() {
.getContainer()
.addEventListener('click', () => {
plugin.removeRealtyScene();
- plugin.addRealtyScene(REALTY_SCENE_1, { modelId: 'ds321ba234cb' });
+ plugin.addRealtyScene(REALTY_SCENE_1, {
+ buildingId: 'ds321ba234cb',
+ });
});
new mapglAPI.Control(map, '', {
diff --git a/demo/mocks.ts b/demo/mocks.ts
index 8413ef0..7281603 100644
--- a/demo/mocks.ts
+++ b/demo/mocks.ts
@@ -7,6 +7,7 @@ export const REALTY_SCENE: BuildingOptions[] = [
modelUrl: 'zgktechnology1.glb',
rotateZ: -15.1240072739039,
linkedIds: ['70030076555823021'],
+ interactive: true,
mapOptions: {
center: [47.24547737708662, 56.134591508663135],
pitch: 40,
@@ -14,7 +15,7 @@ export const REALTY_SCENE: BuildingOptions[] = [
rotation: -41.4,
},
popupOptions: {
- coordinates: [47.24511721603574, 56.13451456056651],
+ coordinates: [47.24498128610925, 56.13451011334241],
title: 'Корпус 1. 11 этажей',
description: 'Срок сдачи: IV кв. 2024 г.
15 мин. пешком до ст. м. Московская',
},
@@ -29,9 +30,10 @@ export const REALTY_SCENE: BuildingOptions[] = [
zoom: 20,
rotation: -57.5,
},
+ isUnderground: true,
poiGroups: [
{
- id: 1111,
+ id: '1111',
type: 'primary',
minZoom: 19.5,
elevation: 5,
@@ -81,7 +83,6 @@ export const REALTY_SCENE: BuildingOptions[] = [
id: '000034',
text: '11',
modelUrl: 'zgktechnology1_floor11.glb',
- isUnderground: true,
mapOptions: {
center: [47.24556663327373, 56.13456998211929],
pitch: 40,
@@ -90,7 +91,7 @@ export const REALTY_SCENE: BuildingOptions[] = [
},
poiGroups: [
{
- id: 1111,
+ id: '1111',
type: 'primary',
minZoom: 19,
elevation: 35,
@@ -142,8 +143,9 @@ export const REALTY_SCENE: BuildingOptions[] = [
modelId: '1ba234cb',
coordinates: [47.245286302641034, 56.134743473834099],
modelUrl: 'zgktechnology2.glb',
- rotateY: -15.1240072739039,
+ rotateZ: -15.1240072739039,
linkedIds: ['70030076555821177'],
+ interactive: true,
mapOptions: {
center: [47.245008950283065, 56.1344698491912],
pitch: 45,
@@ -160,7 +162,6 @@ export const REALTY_SCENE: BuildingOptions[] = [
id: 'aaa777',
text: '2-15',
modelUrl: 'zgktechnology2_floor2.glb',
- isUnderground: true,
mapOptions: {
center: [47.24463456947374, 56.134675042798094],
pitch: 35,
@@ -169,7 +170,7 @@ export const REALTY_SCENE: BuildingOptions[] = [
},
poiGroups: [
{
- id: 1111,
+ id: '1111',
type: 'primary',
minZoom: 19.7,
elevation: 7,
@@ -255,7 +256,7 @@ export const REALTY_SCENE: BuildingOptions[] = [
},
poiGroups: [
{
- id: 1111,
+ id: '1111',
type: 'primary',
minZoom: 18.9,
elevation: 53,
@@ -335,7 +336,7 @@ export const REALTY_SCENE: BuildingOptions[] = [
modelId: 'eda234cb',
coordinates: [47.245286302641034, 56.134743473834099],
modelUrl: 'zgktechnology_construction.glb',
- rotateY: -15.1240072739039,
+ rotateZ: -15.1240072739039,
linkedIds: ['70030076561388553'],
interactive: false,
},
@@ -346,8 +347,9 @@ export const REALTY_SCENE_1: BuildingOptions[] = [
modelId: 'ds321ba234cb',
coordinates: [47.245286302641034, 56.134743473834099],
modelUrl: 'zgktechnology2.glb',
- rotateY: -15.1240072739039,
+ rotateZ: -15.1240072739039,
linkedIds: ['70030076555823021', '70030076555821177', '70030076555823021'],
+ interactive: true,
mapOptions: {
center: [47.245008950283065, 56.1344698491912],
pitch: 45,
@@ -388,7 +390,7 @@ export const REALTY_SCENE_1: BuildingOptions[] = [
modelId: '345feda234cb',
coordinates: [47.245286302641034, 56.134743473834099],
modelUrl: 'zgktechnology_construction.glb',
- rotateY: -15.1240072739039,
+ rotateZ: -15.1240072739039,
linkedIds: ['70030076561388553'],
interactive: false,
},
diff --git a/src/control/index.ts b/src/control/index.ts
index c4134e0..6c2e730 100644
--- a/src/control/index.ts
+++ b/src/control/index.ts
@@ -1,13 +1,11 @@
import type { Map as MapGL, ControlOptions } from '@2gis/mapgl/types';
-
-import type { Id } from '../types/plugin';
-import type { ControlShowOptions } from './types';
+import type { ControlShowOptions, FloorLevel } from './types';
import icon_building from 'raw-loader!./icon_building.svg';
import icon_parking from 'raw-loader!./icon_parking.svg';
-import { Control } from './control';
import classes from './control.module.css';
-import { createCompoundId } from '../utils/common';
+import { Control } from './control';
+import { Id } from '../types';
const content = /* HTML */ `
@@ -31,7 +29,6 @@ const content = /* HTML */ `
* @internal
*/
export class GltfFloorControl extends Control {
- private _map: MapGL;
private _root: HTMLElement;
private _content: HTMLElement;
private _contentHome: HTMLElement;
@@ -41,7 +38,6 @@ export class GltfFloorControl extends Control {
constructor(map: MapGL, options: ControlOptions) {
super(map, content, options);
- this._map = map;
this._root = this._wrap.querySelector(`.${classes.root}`) as HTMLElement;
this._content = this._wrap.querySelector(`.${classes.content}`) as HTMLElement;
this._contentHome = this._wrap.querySelector(`.${classes.contentHome}`) as HTMLElement;
@@ -52,16 +48,16 @@ export class GltfFloorControl extends Control {
public show(options: ControlShowOptions) {
this._removeButtonsEventListeners();
- const { modelId, floorId, floorLevels = [] } = options;
+ const { buildingModelId, activeModelId, floorLevels = [] } = options;
- this._currentFloorId = createCompoundId(modelId, floorId);
+ this._currentFloorId = activeModelId;
this._root.style.display = 'block';
this._content.innerHTML = '';
this._contentHome.innerHTML = '';
let currentButton: HTMLElement | undefined;
- floorLevels.forEach(({ floorId, text, icon }) => {
- const rootContent = floorId === undefined ? this._contentHome : this._content;
+ floorLevels.forEach(({ modelId, text, icon }) => {
+ const rootContent = modelId === buildingModelId ? this._contentHome : this._content;
const button = document.createElement('button');
let buttonContent = text;
if (icon) {
@@ -75,14 +71,13 @@ export class GltfFloorControl extends Control {
}
button.className = classes.control;
button.innerHTML = `
${buttonContent}
`;
- const id = createCompoundId(modelId, floorId);
- button.name = id;
- if (this._currentFloorId === id) {
+ button.name = modelId;
+ if (this._currentFloorId === modelId) {
button.disabled = true;
currentButton = button;
}
- const handler = this._controlHandler(modelId, floorId);
+ const handler = this._controlHandler(modelId);
button.addEventListener('click', handler);
this._handlers.set(button, handler);
@@ -125,22 +120,19 @@ export class GltfFloorControl extends Control {
});
}
- private _controlHandler = (modelId: Id, floorId?: Id) => () => {
- this.switchCurrentFloorLevel(modelId, floorId);
+ private _controlHandler = (modelId: Id) => () => {
+ this._switchCurrentFloorLevel(modelId);
- this.emit('floorChange', {
+ this.emit('floorchange', {
modelId,
- floorId,
});
};
- public switchCurrentFloorLevel(modelId: Id, floorId?: Id) {
+ private _switchCurrentFloorLevel(modelId: Id) {
if (this._currentFloorId === undefined) {
return;
}
- const id = createCompoundId(modelId, floorId);
-
const buttonToDisabled: HTMLButtonElement | null = this._wrap.querySelector(
`.${classes.control}[name="${this._currentFloorId}"]`,
);
@@ -149,12 +141,12 @@ export class GltfFloorControl extends Control {
}
const buttonToEnabled: HTMLButtonElement | null = this._wrap.querySelector(
- `.${classes.control}[name="${id}"]`,
+ `.${classes.control}[name="${modelId}"]`,
);
if (buttonToEnabled) {
buttonToEnabled.disabled = true;
}
- this._currentFloorId = id;
+ this._currentFloorId = modelId;
}
}
diff --git a/src/control/types.ts b/src/control/types.ts
index 11bcdeb..453ce22 100644
--- a/src/control/types.ts
+++ b/src/control/types.ts
@@ -4,7 +4,7 @@ import { Id } from '../types/plugin';
* Floor level data
*/
export interface FloorLevel {
- floorId?: Id;
+ modelId: Id; // id модели этажа или здания
text: string;
icon?: 'parking' | 'building' | string;
}
@@ -13,8 +13,8 @@ export interface FloorLevel {
* Options for the method show
*/
export interface ControlShowOptions {
- modelId: Id;
- floorId?: Id;
+ buildingModelId: Id;
+ activeModelId: Id;
floorLevels?: FloorLevel[];
}
@@ -22,13 +22,12 @@ export interface ControlShowOptions {
* Event that emitted on button presses of the control
*/
export interface FloorChangeEvent {
- modelId: Id;
- floorId?: Id;
+ modelId: Id; // id модели этажа или здания
}
export interface ControlEventTable {
/**
* Emitted when floor's plan was changed
*/
- floorChange: FloorChangeEvent;
+ floorchange: FloorChangeEvent;
}
diff --git a/src/plugin.ts b/src/plugin.ts
index be0a459..bcf96f8 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -5,9 +5,11 @@ import type { Id, PluginOptions, ModelOptions, BuildingState } from './types/plu
import { applyOptionalDefaults } from './utils/common';
import { Evented } from './external/evented';
-// import { RealtyScene } from './realtyScene/realtyScene';
import { defaultOptions } from './defaultOptions';
import { concatUrl, isAbsoluteUrl } from './utils/url';
+import { createModelEventData } from './utils/events';
+import { RealtyScene } from './realtyScene/realtyScene';
+import { GROUND_COVERING_LAYER } from './constants';
interface Model {
instance: any; // GltfModel
@@ -26,7 +28,7 @@ export class GltfPlugin extends Evented
{
private map: MapGL;
private options: Required;
private models: Map;
- // private realtyScene?: RealtyScene;
+ private realtyScene?: RealtyScene;
/**
* The main class of the plugin
@@ -43,8 +45,8 @@ export class GltfPlugin extends Evented {
* modelId: '03a234cb',
* coordinates: [82.886554, 54.980988],
* modelUrl: 'models/cube_draco.glb',
- * rotateX: 90,
- * scale: 1000,
+ * rotateZ: 90,
+ * scale: 2,
* },
* ]);
* ```
@@ -57,8 +59,15 @@ export class GltfPlugin extends Evented {
this.map = map;
this.options = applyOptionalDefaults(pluginOptions ?? {}, defaultOptions);
this.models = new Map();
+
+ map.on('styleload', () => {
+ this.map.addLayer(GROUND_COVERING_LAYER); // мб унести отсюда в RealtyScene, нужно подумать
+ // this.poiGroups.onMapStyleUpdate();
+ });
}
+ // public destroy() {}
+
public setOptions(pluginOptions: Pick, 'groundCoveringColor'>) {
Object.keys(pluginOptions).forEach((option) => {
switch (option) {
@@ -71,8 +80,8 @@ export class GltfPlugin extends Evented {
});
}
- public async addModel(modelToLoad: ModelOptions, showOnLoad = true) {
- return this.addModels([modelToLoad], showOnLoad ? [modelToLoad.modelId] : []);
+ public async addModel(modelToLoad: ModelOptions, hideOnLoad = false) {
+ return this.addModels([modelToLoad], hideOnLoad ? [] : [modelToLoad.modelId]);
}
public async addModels(modelsToLoad: ModelOptions[], modelIdsToShow?: Id[]) {
@@ -123,6 +132,13 @@ export class GltfPlugin extends Evented {
return new Promise((resolve) => {
instance.once('modelloaded', () => resolve(model));
+ (['click', 'mousemove', 'mouseover', 'mouseout'] as const).forEach(
+ (eventType) => {
+ instance.on(eventType, (ev) => {
+ this.emit(eventType, createModelEventData(ev, options));
+ });
+ },
+ );
});
});
@@ -139,6 +155,10 @@ export class GltfPlugin extends Evented {
});
}
+ public isModelAdded(id: Id) {
+ return this.models.has(id);
+ }
+
public removeModel(id: Id) {
const model = this.models.get(id);
if (model) {
@@ -168,21 +188,16 @@ export class GltfPlugin extends Evented {
}
public async addRealtyScene(scene: BuildingOptions[], state?: BuildingState) {
- // this.realtyScene = new RealtyScene(
- // this,
- // this.map,
- // this.eventSource,
- // this.models,
- // this.options,
- // );
- // return this.realtyScene.add(scene, state);
+ this.realtyScene = new RealtyScene(this, this.map, this.options);
+ return this.realtyScene.init(scene, state);
}
- public removeRealtyScene(preserveCache?: boolean) {
- // if (!this.realtyScene) {
- // return;
- // }
- // this.realtyScene.destroy(preserveCache);
- // this.realtyScene = undefined;
+ // public showRealtyScene() {}
+
+ // public hideRealtyScene() {}
+
+ public removeRealtyScene() {
+ this.realtyScene?.destroy();
+ this.realtyScene = undefined;
}
}
diff --git a/src/realtyScene/realtyScene.ts b/src/realtyScene/realtyScene.ts
index 633d7da..f21f62f 100644
--- a/src/realtyScene/realtyScene.ts
+++ b/src/realtyScene/realtyScene.ts
@@ -1,663 +1,512 @@
-// import type { Map as MapGL, AnimationOptions, HtmlMarker, GeoJsonSource } from '@2gis/mapgl/types';
-
-// import { GltfPlugin } from '../plugin';
-// import { defaultOptions } from '../defaultOptions';
-// import { GltfFloorControl } from '../control';
-// import { clone, createCompoundId } from '../utils/common';
-// import classes from './realtyScene.module.css';
-
-// import type { Id, BuildingState, ModelOptions } from '../types/plugin';
-// import type {
-// BuildingOptions,
-// MapOptions,
-// BuildingFloorOptions,
-// PopupOptions,
-// } from '../types/realtyScene';
-// import type { ControlShowOptions, FloorLevel, FloorChangeEvent } from '../control/types';
-// import type {
-// GltfPluginModelEvent,
-// GltfPluginPoiEvent,
-// PoiGeoJsonProperties,
-// } from '../types/events';
-// import { GROUND_COVERING_LAYER, GROUND_COVERING_SOURCE_DATA, GROUND_COVERING_SOURCE_PURPOSE } from '../constants';
-
-// export class RealtyScene {
-// private activeBuilding?: BuildingOptions;
-// private activeModelId?: Id;
-// private control?: GltfFloorControl;
-// private activePoiGroupIds: Id[] = [];
-// private container: HTMLElement;
-// private buildingFacadeIds: Id[] = [];
-// // this field is needed when the highlighted
-// // model is placed under the floors' control
-// private prevHoveredModelId: Id | null = null;
-// private popup: HtmlMarker | null = null;
-// private scene: BuildingOptions[] | null = null;
-// private groundCoveringSource: GeoJsonSource;
-// private undergroundFloors = new Set();
-// private poiGroups: PoiGroups;
-
-// constructor(
-// private plugin: GltfPlugin,
-// private map: MapGL,
-// private eventSource: EventSource,
-// private models: Map,
-// private options: typeof defaultOptions,
-// ) {
-// this.poiGroups = new PoiGroups(this.map, this.options.poiConfig);
-// this.container = map.getContainer();
-// this.groundCoveringSource = new mapgl.GeoJsonSource(map, {
-// maxZoom: 2,
-// data: GROUND_COVERING_SOURCE_DATA,
-// attributes: {
-// purpose: GROUND_COVERING_SOURCE_PURPOSE,
-// },
-// });
-
-// map.on('styleload', () => {
-// this.map.addLayer(GROUND_COVERING_LAYER);
-// this.poiGroups.onMapStyleUpdate();})
-// }
-
-// public async add(scene: BuildingOptions[], originalState?: BuildingState) {
-// // make unique compound identifiers for floor's plans
-// let state = originalState === undefined ? originalState : clone(originalState);
-// this.makeUniqueFloorIds(scene);
-// if (state?.floorId !== undefined) {
-// state.floorId = createCompoundId(state.modelId, state.floorId);
-// }
-
-// // set initial fields
-// if (state !== undefined) {
-// this.activeBuilding = scene.find((model) => model.modelId === state?.modelId);
-// if (this.activeBuilding === undefined) {
-// throw new Error(
-// `There is no building's model with id ${state.modelId}. ` +
-// `Please check options of method addRealtyScene`,
-// );
-// }
-// this.activeModelId =
-// state.floorId !== undefined ? state.floorId : this.activeBuilding.modelId;
-// }
-
-// // initialize initial scene
-// const models: ModelOptions[] = [];
-// const modelIds: Id[] = [];
-// this.scene = scene;
-// scene.forEach((scenePart) => {
-// this.buildingFacadeIds.push(scenePart.modelId);
-// const modelOptions = getBuildingModelOptions(scenePart);
-// const floors = scenePart.floors ?? [];
-// let hasFloorByDefault = false;
-
-// for (let floor of floors) {
-// if (floor.isUnderground) {
-// this.undergroundFloors.add(floor.id);
-// }
-
-// if (state?.floorId !== undefined && floor.id === state.floorId) {
-// // for convenience push original building
-// models.push(modelOptions);
-// // push modified options for floor
-// models.push(getFloorModelOptions(floor, scenePart));
-// modelIds.push(floor.id);
-// hasFloorByDefault = true;
-// }
-// }
-
-// if (!hasFloorByDefault) {
-// models.push(modelOptions);
-// modelIds.push(scenePart.modelId);
-// }
-
-// if (this.options.modelsLoadStrategy === 'waitAll') {
-// for (let floor of floors) {
-// if (floor.id === state?.floorId) {
-// continue;
-// }
-// models.push(getFloorModelOptions(floor, scenePart));
-// }
-// }
-// });
-
-// // Leave only the underground floor's plan to be shown
-// if (state?.floorId !== undefined && this.undergroundFloors.has(state.floorId)) {
-// modelIds.length = 0;
-// modelIds.push(state.floorId);
-// }
-
-// return this.plugin.addModelsPartially(models, modelIds).then(() => {
-// // set options after adding models
-// if (state?.floorId !== undefined) {
-// const floors = this.activeBuilding?.floors ?? [];
-// const activeFloor = floors.find((floor) => floor.id === state?.floorId);
-// this.setMapOptions(activeFloor?.mapOptions);
-// this.addFloorPoi(activeFloor);
-// if (this.undergroundFloors.has(state.floorId)) {
-// this.switchOnGroundCovering();
-// }
-// } else {
-// this.setMapOptions(this.activeBuilding?.mapOptions);
-// }
-
-// // initialize floors' control
-// const { position } = this.options.floorsControl;
-// this.control = new GltfFloorControl(this.map, { position });
-// if (state !== undefined) {
-// const controlOptions = this.createControlOptions(scene, state);
-// this.control?.show(controlOptions);
-// if (state.floorId) {
-// this.eventSource.setCurrentFloorId(state.floorId);
-// }
-// }
-
-// // bind all events
-// this.bindRealtySceneEvents();
-// });
-// }
-
-// public resetGroundCoveringColor() {
-// const attrs = this.groundCoveringSource.getAttributes();
-// if ('color' in attrs) {
-// this.groundCoveringSource.setAttributes({
-// ...attrs,
-// color: this.options.groundCoveringColor,
-// });
-// }
-// }
-
-// public isUndergroundFloorShown() {
-// return this.activeModelId !== undefined && this.undergroundFloors.has(this.activeModelId);
-// }
-
-// public destroy(preserveCache?: boolean) {
-// this.unbindRealtySceneEvents();
-
-// this.plugin.removeModels(
-// this.scene?.reduce((agg, opts) => {
-// agg.push(opts.modelId);
-// opts.floors?.forEach((floor) => agg.push(floor.id));
-
-// return agg;
-// }, []) ?? [],
-// preserveCache,
-// );
-
-// this.clearPoiGroups();
-// this.eventSource.setCurrentFloorId(null);
-
-// this.groundCoveringSource.destroy();
-// this.undergroundFloors.clear();
-
-// this.control?.destroy();
-// this.control = undefined;
-
-// this.popup?.destroy();
-// this.popup = null;
-
-// this.activeBuilding = undefined;
-// this.activeModelId = undefined;
-// this.activePoiGroupIds = [];
-// this.buildingFacadeIds = [];
-// this.prevHoveredModelId = null;
-// this.scene = null;
-// }
-
-// private bindRealtySceneEvents() {
-// this.plugin.on('click', this.onSceneClick);
-// this.plugin.on('mouseover', this.onSceneMouseOver);
-// this.plugin.on('mouseout', this.onSceneMouseOut);
-
-// this.control?.on('floorChange', this.floorChangeHandler);
-// }
-
-// private unbindRealtySceneEvents() {
-// this.plugin.off('click', this.onSceneClick);
-// this.plugin.off('mouseover', this.onSceneMouseOver);
-// this.plugin.off('mouseout', this.onSceneMouseOut);
-
-// this.control?.off('floorChange', this.floorChangeHandler);
-// }
-
-// private createControlOptions(scene: BuildingOptions[], buildingState: BuildingState) {
-// const { modelId, floorId } = buildingState;
-// const options: ControlShowOptions = {
-// modelId: modelId,
-// };
-// if (floorId !== undefined) {
-// options.floorId = floorId;
-// }
-
-// const buildingData = scene.find((scenePart) => scenePart.modelId === modelId);
-// if (!buildingData) {
-// return options;
-// }
-
-// if (buildingData.floors !== undefined) {
-// const floorLevels: FloorLevel[] = [
-// {
-// icon: 'building',
-// text: '',
-// },
-// ];
-// buildingData.floors.forEach((floor) => {
-// floorLevels.push({
-// floorId: floor.id,
-// text: floor.text,
-// });
-// });
-// options.floorLevels = floorLevels;
-// }
-// return options;
-// }
-
-// private setMapOptions(options?: MapOptions) {
-// if (!options) {
-// return;
-// }
-
-// const animationOptions: AnimationOptions = {
-// easing: 'easeInSine',
-// duration: 500,
-// };
-// if (options.center) {
-// this.map.setCenter(options.center, animationOptions);
-// }
-// if (options.pitch) {
-// this.map.setPitch(options.pitch, animationOptions);
-// }
-// if (options.rotation) {
-// this.map.setRotation(options.rotation, animationOptions);
-// }
-// if (options.zoom) {
-// this.map.setZoom(options.zoom, animationOptions);
-// }
-// }
-
-// // checks if the modelId is external facade of the building
-// private isFacadeBuilding(modelId?: Id): modelId is Id {
-// if (modelId === undefined) {
-// return false;
-// }
-
-// return this.buildingFacadeIds.includes(modelId);
-// }
-
-// private getPopupOptions(modelId: Id): PopupOptions | undefined {
-// if (this.scene === null) {
-// return;
-// }
-// let building = this.scene.find((building) => building.modelId === modelId);
-// if (building === undefined) {
-// return;
-// }
-// return building.popupOptions;
-// }
-
-// private onSceneMouseOver = (ev: GltfPluginPoiEvent | GltfPluginModelEvent) => {
-// if (ev.target.type === 'model') {
-// const id = ev.target.modelId;
-// if (this.isFacadeBuilding(id)) {
-// this.container.style.cursor = 'pointer';
-// this.toggleHighlightModel(id);
-// let popupOptions = this.getPopupOptions(id);
-// if (popupOptions) {
-// this.showPopup(popupOptions);
-// }
-// }
-// }
-// };
-// private onSceneMouseOut = (ev: GltfPluginPoiEvent | GltfPluginModelEvent) => {
-// if (ev.target.type === 'model') {
-// const id = ev.target.modelId;
-// if (this.isFacadeBuilding(id)) {
-// this.container.style.cursor = '';
-// this.hidePopup();
-// if (this.prevHoveredModelId !== null) {
-// this.toggleHighlightModel(id);
-// }
-// }
-// }
-// };
-
-// private onSceneClick = (ev: GltfPluginPoiEvent | GltfPluginModelEvent) => {
-// if (this.scene === null) {
-// return;
-// }
-
-// if (ev.target.type === 'model') {
-// const id = ev.target.modelId;
-// if (this.isFacadeBuilding(id)) {
-// this.buildingClickHandler(this.scene, id);
-// }
-// }
-
-// if (ev.target.type === 'poi') {
-// this.poiClickHandler(ev.target.data);
-// }
-// };
-
-// private poiClickHandler = (data: PoiGeoJsonProperties) => {
-// const url: string | undefined = data.userData.url;
-// if (url !== undefined) {
-// const a = document.createElement('a');
-// a.setAttribute('href', url);
-// a.setAttribute('target', '_blank');
-// a.click();
-// }
-// };
-
-// private floorChangeHandler = (ev: FloorChangeEvent) => {
-// const model = this.activeBuilding;
-// if (model !== undefined && model.floors !== undefined) {
-// if (this.popup !== null) {
-// this.popup.destroy();
-// }
-
-// // click to the building button
-// if (ev.floorId === undefined) {
-// if (this.prevHoveredModelId !== null) {
-// this.toggleHighlightModel(this.prevHoveredModelId);
-// }
-
-// this.clearPoiGroups();
-// const modelsToAdd: ModelOptions[] = this.isUndergroundFloorShown()
-// ? (this.scene ?? []).map((scenePart) => getBuildingModelOptions(scenePart))
-// : [getBuildingModelOptions(model)];
-
-// this.plugin.addModels(modelsToAdd).then(() => {
-// if (this.activeModelId !== undefined) {
-// this.plugin.removeModel(this.activeModelId, true);
-// if (this.isUndergroundFloorShown()) {
-// this.switchOffGroundCovering();
-// }
-// }
-// this.setMapOptions(model?.mapOptions);
-// this.activeModelId = model.modelId;
-// });
-// }
-// // click to the floor button
-// if (ev.floorId !== undefined) {
-// const selectedFloor = model.floors.find((floor) => floor.id === ev.floorId);
-// if (selectedFloor !== undefined && this.activeModelId !== undefined) {
-// const selectedFloorModelOption = getFloorModelOptions(selectedFloor, model);
-
-// // In case of underground -> underground and ground -> ground transitions just switch floor's plan
-// if (this.isUndergroundFloorShown() === Boolean(selectedFloor.isUnderground)) {
-// this.plugin.addModel(selectedFloorModelOption).then(() => {
-// if (this.activeModelId !== undefined) {
-// this.plugin.removeModel(this.activeModelId, true);
-// }
-// this.addFloorPoi(selectedFloor);
-// });
-
-// return;
-// }
-
-// const modelsToAdd: ModelOptions[] = this.isUndergroundFloorShown()
-// ? (this.scene ?? [])
-// .filter((scenePart) => scenePart.modelId !== model.modelId)
-// .map((scenePart) => getBuildingModelOptions(scenePart))
-// : [];
-
-// modelsToAdd.push(selectedFloorModelOption);
-
-// const modelsToRemove = this.isUndergroundFloorShown()
-// ? []
-// : (this.scene ?? [])
-// .filter((scenePart) => scenePart.modelId !== model.modelId)
-// .map((scenePart) => scenePart.modelId);
-
-// modelsToRemove.push(this.activeModelId);
-
-// this.plugin.addModels(modelsToAdd).then(() => {
-// this.plugin.removeModels(modelsToRemove, true);
-// this.isUndergroundFloorShown()
-// ? this.switchOffGroundCovering()
-// : this.switchOnGroundCovering();
-// this.addFloorPoi(selectedFloor);
-// });
-// }
-// }
-// }
-// };
-
-// private buildingClickHandler = (scene: BuildingOptions[], modelId: Id) => {
-// const selectedBuilding = scene.find((model) => model.modelId === modelId);
-// if (selectedBuilding === undefined) {
-// return;
-// }
-
-// // don't show the pointer cursor on the model when user
-// // started to interact with the building
-// this.container.style.cursor = '';
-
-// if (this.popup !== null) {
-// this.popup.destroy();
-// }
-
-// // if there is a visible floor plan, then show the external
-// // facade of the active building before focusing on the new building
-// if (
-// this.activeBuilding &&
-// this.activeModelId &&
-// this.activeModelId !== this.activeBuilding?.modelId
-// ) {
-// // User is able to click on any other buildings as long as ground floor's plan is shown
-// // because when underground floor's plan is shown other buildings are hidden.
-// const oldId = this.activeModelId;
-// this.plugin.addModel(getBuildingModelOptions(this.activeBuilding)).then(() => {
-// this.clearPoiGroups();
-// this.plugin.removeModel(oldId, true);
-// });
-// }
-
-// // show the highest floor after a click on the building
-// const floors = selectedBuilding.floors ?? [];
-// if (floors.length !== 0) {
-// const floorOptions = floors[floors.length - 1];
-
-// const modelsToRemove = floorOptions.isUnderground
-// ? scene.map((scenePart) => scenePart.modelId)
-// : [selectedBuilding.modelId];
-
-// this.plugin.addModel(getFloorModelOptions(floorOptions, selectedBuilding)).then(() => {
-// this.plugin.removeModels(modelsToRemove, true);
-// if (floorOptions.isUnderground) {
-// this.switchOnGroundCovering();
-// }
-// this.addFloorPoi(floorOptions);
-// this.control?.switchCurrentFloorLevel(selectedBuilding.modelId, floorOptions.id);
-// });
-// } else {
-// this.activeModelId = selectedBuilding.modelId;
-// this.setMapOptions(selectedBuilding.mapOptions);
-// }
-
-// if (
-// this.activeBuilding === undefined ||
-// selectedBuilding.modelId !== this.activeBuilding?.modelId
-// ) {
-// // initialize control
-// const { position } = this.options.floorsControl;
-// this.control?.destroy();
-// this.control = new GltfFloorControl(this.map, { position });
-// const state = { modelId: selectedBuilding.modelId };
-// const controlOptions = this.createControlOptions(scene, state);
-// this.control?.show(controlOptions);
-// this.control.on('floorChange', (ev) => {
-// this.floorChangeHandler(ev);
-// });
-// }
-
-// this.activeBuilding = selectedBuilding;
-// };
-
-// private addFloorPoi(floorOptions?: BuildingFloorOptions) {
-// if (floorOptions === undefined) {
-// return;
-// }
-
-// this.activeModelId = floorOptions.id;
-
-// this.setMapOptions(floorOptions?.mapOptions);
-
-// this.clearPoiGroups();
-
-// floorOptions.poiGroups?.forEach((poiGroup) => {
-// if (this.activeBuilding?.modelId) {
-// this.plugin.addPoiGroup(poiGroup, {
-// modelId: this.activeBuilding?.modelId,
-// floorId: floorOptions.id,
-// });
-// this.activePoiGroupIds.push(poiGroup.id);
-// }
-// });
-// }
-
-// private clearPoiGroups() {
-// this.activePoiGroupIds.forEach((id) => {
-// this.plugin.removePoiGroup(id);
-// });
-
-// this.activePoiGroupIds = [];
-// }
-
-// /**
-// * Add the group of poi to the map
-// *
-// * @param options Options of the group of poi to add to the map
-// * @param state State of the active building to connect with added the group of poi
-// */
-// public async addPoiGroup(options: PoiGroupOptions, state?: BuildingState) {
-// this.poiGroups.add(options, state);
-// }
-
-// /**
-// * Remove the group of poi from the map
-// *
-// * @param id Identifier of the group of poi to remove
-// */
-// public removePoiGroup(id: Id) {
-// this.poiGroups.remove(id);
-// }
-
-// // TODO: Don't mutate scene data.
-// private makeUniqueFloorIds(scene: BuildingOptions[]) {
-// for (let scenePart of scene) {
-// const floors = scenePart.floors ?? [];
-// for (let floor of floors) {
-// if (!floor.id.toString().startsWith(scenePart.modelId.toString())) {
-// floor.id = createCompoundId(scenePart.modelId, floor.id);
-// }
-// }
-// }
-// }
-
-// public toggleHighlightModel(modelId: Id) {
-// // skip toggle if user is using default emissiveIntensity
-// // that means that model won't be hovered
-// const { intencity } = this.options.hoverHighlight;
-// if (intencity === 0) {
-// return;
-// }
-
-// const model = this.models.get(String(modelId));
-
-// if (model === undefined) {
-// return;
-// }
-
-// let shouldUnsetFlag = false;
-// model.traverse((obj) => {
-// if (obj instanceof THREE.Mesh) {
-// if (modelId === this.prevHoveredModelId) {
-// obj.material.emissiveIntensity = 0.0;
-// shouldUnsetFlag = true;
-// } else {
-// obj.material.emissiveIntensity = intencity;
-// }
-// }
-// });
-
-// this.prevHoveredModelId = shouldUnsetFlag ? null : modelId;
-// this.map.triggerRerender();
-// }
-
-// private showPopup(options: PopupOptions) {
-// this.popup = new mapgl.HtmlMarker(this.map, {
-// coordinates: options.coordinates,
-// html: this.getPopupHtml(options),
-// });
-// }
-
-// private hidePopup() {
-// if (this.popup !== null) {
-// this.popup.destroy();
-// this.popup = null;
-// }
-// }
-
-// private getPopupHtml(data: PopupOptions) {
-// if (data.description === undefined) {
-// return ``;
-// }
-
-// return ``;
-// }
-
-// private switchOffGroundCovering() {
-// const attrs = { ...this.groundCoveringSource.getAttributes() };
-// delete attrs['color'];
-// this.groundCoveringSource.setAttributes(attrs);
-// }
-
-// private switchOnGroundCovering() {
-// this.groundCoveringSource.setAttributes({
-// ...this.groundCoveringSource.getAttributes(),
-// color: this.options.groundCoveringColor,
-// });
-// }
-// }
-
-// function getBuildingModelOptions(building: BuildingOptions): ModelOptions {
-// return {
-// modelId: building.modelId,
-// coordinates: building.coordinates,
-// modelUrl: building.modelUrl,
-// rotateX: building.rotateX,
-// rotateY: building.rotateY,
-// rotateZ: building.rotateZ,
-// offsetX: building.offsetX,
-// offsetY: building.offsetY,
-// offsetZ: building.offsetZ,
-// scale: building.scale,
-// linkedIds: building.linkedIds,
-// interactive: building.interactive,
-// };
-// }
-
-// function getFloorModelOptions(
-// floor: BuildingFloorOptions,
-// building: BuildingOptions,
-// ): ModelOptions {
-// return {
-// modelId: floor.id,
-// coordinates: building.coordinates,
-// modelUrl: floor.modelUrl,
-// rotateX: building.rotateX,
-// rotateY: building.rotateY,
-// rotateZ: building.rotateZ,
-// offsetX: building.offsetX,
-// offsetY: building.offsetY,
-// offsetZ: building.offsetZ,
-// scale: building.scale,
-// linkedIds: building.linkedIds,
-// interactive: building.interactive,
-// };
-// }
+import type { Map as MapGL, AnimationOptions, HtmlMarker, GeoJsonSource } from '@2gis/mapgl/types';
+
+import { GltfPlugin } from '../plugin';
+import { GltfFloorControl } from '../control';
+import classes from './realtyScene.module.css';
+
+import type { BuildingState, Id, ModelOptions, PluginOptions } from '../types/plugin';
+import type {
+ BuildingOptions,
+ MapOptions,
+ BuildingFloorOptions,
+ PopupOptions,
+} from '../types/realtyScene';
+import type { FloorLevel, FloorChangeEvent } from '../control/types';
+import type { GltfPluginModelEvent, GltfPluginPoiEvent } from '../types/events';
+import { GROUND_COVERING_SOURCE_DATA, GROUND_COVERING_SOURCE_PURPOSE } from '../constants';
+
+interface RealtySceneState {
+ activeModelId?: Id;
+
+ // id здания мапится на опции здания или опции этажа этого здания
+ buildingVisibility: Map;
+}
+
+type BuildingOptionsInternal = Omit & {
+ floors: FloorLevel[];
+};
+type BuildingFloorOptionsInternal = BuildingFloorOptions & {
+ buildingOptions: ModelOptions;
+};
+
+export class RealtyScene {
+ private buildings = new Map();
+ private floors = new Map();
+ private undergroundFloors = new Set();
+ private state: RealtySceneState = {
+ activeModelId: undefined,
+ buildingVisibility: new Map(),
+ };
+
+ private groundCoveringSource: GeoJsonSource;
+ private control: GltfFloorControl;
+ private popup?: HtmlMarker;
+
+ // private poiGroups: PoiGroups;
+
+ constructor(
+ private plugin: GltfPlugin,
+ private map: MapGL,
+ private options: Required,
+ ) {
+ const { position } = this.options.floorsControl;
+ this.control = new GltfFloorControl(this.map, { position });
+ // this.poiGroups = new PoiGroups(this.map, this.options.poiConfig);
+ this.groundCoveringSource = new mapgl.GeoJsonSource(map, {
+ maxZoom: 2,
+ data: GROUND_COVERING_SOURCE_DATA,
+ attributes: {
+ purpose: GROUND_COVERING_SOURCE_PURPOSE,
+ },
+ });
+ }
+
+ private getBuildingModelId(id: Id | undefined) {
+ if (id === undefined) {
+ return;
+ }
+
+ if (this.buildings.has(id)) {
+ return id;
+ } else {
+ const floor = this.floors.get(id);
+ if (floor) {
+ return floor.buildingOptions.modelId;
+ }
+ }
+ }
+
+ private setState(newState: RealtySceneState) {
+ const prevState = this.state;
+
+ this.buildings.forEach((_, buildingId) => {
+ const prevModelOptions = prevState.buildingVisibility.get(buildingId);
+ const newModelOptions = newState.buildingVisibility.get(buildingId);
+ if (prevModelOptions) {
+ this.plugin.hideModel(prevModelOptions.modelId);
+ }
+
+ if (newModelOptions) {
+ this.plugin.isModelAdded(newModelOptions.modelId)
+ ? this.plugin.showModel(newModelOptions.modelId)
+ : this.plugin.addModel(newModelOptions);
+ }
+ });
+
+ if (prevState.activeModelId !== newState.activeModelId) {
+ if (
+ prevState.activeModelId !== undefined &&
+ this.undergroundFloors.has(prevState.activeModelId)
+ ) {
+ this.switchOffGroundCovering();
+ }
+
+ if (newState.activeModelId !== undefined) {
+ const options =
+ this.buildings.get(newState.activeModelId) ??
+ this.floors.get(newState.activeModelId);
+ if (options) {
+ this.setMapOptions(options.mapOptions);
+ // this.addFloorPoi(activeFloor);
+ // this.clearPoiGroups();
+ }
+
+ if (this.undergroundFloors.has(newState.activeModelId)) {
+ this.switchOnGroundCovering();
+ }
+ }
+ }
+
+ const prevBuildingModelId = this.getBuildingModelId(prevState.activeModelId);
+ const newBuildingModelId = this.getBuildingModelId(newState.activeModelId);
+
+ if (prevBuildingModelId !== newBuildingModelId) {
+ if (newBuildingModelId !== undefined && newState.activeModelId !== undefined) {
+ const buildingOptions = this.buildings.get(newBuildingModelId);
+ if (buildingOptions) {
+ this.control.show({
+ buildingModelId: buildingOptions.modelId,
+ activeModelId: newState.activeModelId,
+ floorLevels: [
+ {
+ modelId: buildingOptions.modelId,
+ icon: 'building',
+ text: '',
+ },
+ ...buildingOptions.floors,
+ ],
+ });
+ }
+ }
+ }
+
+ this.state = newState;
+ }
+
+ public async init(scene: BuildingOptions[], state?: BuildingState) {
+ // Приводим стейт пользователя к внутреннему виду id
+ let activeModelId: Id | undefined = state
+ ? state.floorId
+ ? getFloorModelId(state.buildingId, state.floorId)
+ : state.buildingId
+ : undefined;
+
+ scene.forEach((building) => {
+ const { floors, ...buildingPart } = building;
+ const internalBuilding: BuildingOptionsInternal = {
+ ...buildingPart,
+ floors: [],
+ };
+ const buildingOptions = getBuildingModelOptions(internalBuilding);
+
+ (floors ?? []).forEach((floor) => {
+ const floorModelId = getFloorModelId(building.modelId, floor.id);
+ internalBuilding.floors.push({
+ modelId: floorModelId,
+ text: floor.text,
+ icon: floor.icon,
+ });
+
+ this.floors.set(floorModelId, {
+ ...floor,
+ buildingOptions: buildingOptions,
+ });
+
+ if (floor.isUnderground) {
+ this.undergroundFloors.add(floorModelId);
+ }
+ });
+
+ this.buildings.set(building.modelId, internalBuilding);
+ });
+
+ // Оставляем только существующее значение из переданных modelId в scene
+ activeModelId =
+ activeModelId !== undefined &&
+ (this.buildings.has(activeModelId) || this.floors.has(activeModelId))
+ ? activeModelId
+ : undefined;
+
+ const modelsToLoad: Map = new Map();
+ const buildingVisibility: Map = new Map();
+
+ this.buildings.forEach((options, id) => {
+ const modelOptions = getBuildingModelOptions(options);
+ modelsToLoad.set(id, modelOptions);
+ buildingVisibility.set(id, modelOptions);
+ });
+
+ if (activeModelId) {
+ const floorOptions = this.floors.get(activeModelId);
+ if (floorOptions) {
+ if (this.undergroundFloors.has(activeModelId)) {
+ buildingVisibility.clear(); // показываем только подземный этаж
+ }
+
+ const modelOptions = getFloorModelOptions(floorOptions);
+ buildingVisibility.set(floorOptions.buildingOptions.modelId, modelOptions);
+ modelsToLoad.set(activeModelId, modelOptions);
+ }
+ }
+
+ if (this.options.modelsLoadStrategy === 'waitAll') {
+ this.floors.forEach((options, id) =>
+ modelsToLoad.set(id, getFloorModelOptions(options)),
+ );
+ }
+
+ return this.plugin
+ .addModels(Array.from(modelsToLoad.values()), Array.from(buildingVisibility.keys()))
+ .then(() => {
+ this.setState({
+ activeModelId,
+ buildingVisibility,
+ });
+
+ this.plugin.on('click', this.onSceneClick);
+ this.plugin.on('mouseover', this.onSceneMouseOver);
+ this.plugin.on('mouseout', this.onSceneMouseOut);
+ this.control.on('floorchange', this.floorChangeHandler);
+ });
+ }
+
+ public resetGroundCoveringColor() {
+ const attrs = this.groundCoveringSource.getAttributes();
+ if ('color' in attrs) {
+ this.groundCoveringSource.setAttributes({
+ ...attrs,
+ color: this.options.groundCoveringColor,
+ });
+ }
+ }
+
+ public destroy() {
+ this.plugin.off('click', this.onSceneClick);
+ this.plugin.off('mouseover', this.onSceneMouseOver);
+ this.plugin.off('mouseout', this.onSceneMouseOut);
+ this.control.off('floorchange', this.floorChangeHandler);
+
+ this.plugin.removeModels([...this.buildings.keys(), ...this.floors.keys()]);
+
+ // this.clearPoiGroups();
+
+ this.groundCoveringSource.destroy();
+ this.undergroundFloors.clear();
+
+ this.control.destroy();
+
+ this.popup?.destroy();
+ this.popup = undefined;
+
+ this.state.activeModelId = undefined;
+ this.state.buildingVisibility.clear();
+ this.buildings.clear();
+ this.floors.clear();
+ }
+
+ private setMapOptions(options?: MapOptions) {
+ if (!options) {
+ return;
+ }
+
+ const animationOptions: AnimationOptions = {
+ easing: 'easeInSine',
+ duration: 500,
+ };
+
+ if (options.center) {
+ this.map.setCenter(options.center, animationOptions);
+ }
+ if (options.pitch) {
+ this.map.setPitch(options.pitch, animationOptions);
+ }
+ if (options.rotation) {
+ this.map.setRotation(options.rotation, animationOptions);
+ }
+ if (options.zoom) {
+ this.map.setZoom(options.zoom, animationOptions);
+ }
+ }
+
+ private onSceneMouseOut = (ev: GltfPluginPoiEvent | GltfPluginModelEvent) => {
+ if (ev.target.type !== 'model') {
+ return;
+ }
+
+ this.popup?.destroy();
+ };
+
+ private onSceneMouseOver = ({ target }: GltfPluginPoiEvent | GltfPluginModelEvent) => {
+ if (target.type === 'poi' || target.modelId === undefined) {
+ return;
+ }
+
+ const options = this.buildings.get(target.modelId);
+ if (!options || !options.popupOptions) {
+ return;
+ }
+
+ this.popup = new mapgl.HtmlMarker(this.map, {
+ coordinates: options.popupOptions.coordinates,
+ html: getPopupHtml(options.popupOptions),
+ interactive: false,
+ });
+ };
+
+ private onSceneClick = ({ target }: GltfPluginPoiEvent | GltfPluginModelEvent) => {
+ if (target.type === 'model') {
+ const options = this.buildings.get(target.modelId);
+ if (options) {
+ this.buildingClickHandler(target.modelId);
+ }
+ } else if (target.type === 'poi') {
+ const userData = target.data.userData;
+ if (isObject(userData) && typeof userData.url === 'string') {
+ const a = document.createElement('a');
+ a.setAttribute('href', userData.url);
+ a.setAttribute('target', '_blank');
+ a.click();
+ }
+ }
+ };
+
+ private floorChangeHandler = (ev: FloorChangeEvent) => {
+ const buildingVisibility: Map = new Map();
+ this.buildings.forEach((options, id) => {
+ buildingVisibility.set(id, getBuildingModelOptions(options));
+ });
+ const buildingOptions = this.buildings.get(ev.modelId);
+ if (buildingOptions) {
+ this.setState({
+ activeModelId: ev.modelId,
+ buildingVisibility,
+ });
+ return;
+ }
+
+ const floorOptions = this.floors.get(ev.modelId);
+ if (floorOptions) {
+ if (this.undergroundFloors.has(ev.modelId)) {
+ buildingVisibility.clear();
+ }
+ buildingVisibility.set(
+ floorOptions.buildingOptions.modelId,
+ getFloorModelOptions(floorOptions),
+ );
+ this.setState({
+ activeModelId: ev.modelId,
+ buildingVisibility,
+ });
+ return;
+ }
+ };
+
+ private buildingClickHandler = (modelId: Id) => {
+ const buildingOptions = this.buildings.get(modelId);
+ if (!buildingOptions) {
+ return;
+ }
+
+ let activeModelId = modelId;
+ const buildingVisibility: Map = new Map();
+ this.buildings.forEach((options, id) => {
+ buildingVisibility.set(id, getBuildingModelOptions(options));
+ });
+
+ // показываем самый высокий этаж здания после клика
+ const floors = buildingOptions.floors ?? [];
+ if (floors.length) {
+ const { modelId: floorModelId } = floors[floors.length - 1];
+ const floorOptions = this.floors.get(floorModelId);
+ if (floorOptions) {
+ activeModelId = floorModelId;
+ if (this.undergroundFloors.has(floorModelId)) {
+ buildingVisibility.clear();
+ }
+ buildingVisibility.set(
+ floorOptions.buildingOptions.modelId,
+ getFloorModelOptions(floorOptions),
+ );
+ }
+ }
+
+ this.setState({
+ buildingVisibility,
+ activeModelId,
+ });
+ };
+
+ // private addFloorPoi(floorOptions?: BuildingFloorOptions) {
+ // if (floorOptions === undefined) {
+ // return;
+ // }
+
+ // this.activeModelId = floorOptions.id;
+
+ // this.setMapOptions(floorOptions?.mapOptions);
+
+ // this.clearPoiGroups();
+
+ // floorOptions.poiGroups?.forEach((poiGroup) => {
+ // if (this.activeBuilding?.modelId) {
+ // this.plugin.addPoiGroup(poiGroup, {
+ // modelId: this.activeBuilding?.modelId,
+ // floorId: floorOptions.id,
+ // });
+ // this.activePoiGroupIds.push(poiGroup.id);
+ // }
+ // });
+ // }
+
+ // private clearPoiGroups() {
+ // this.activePoiGroupIds.forEach((id) => {
+ // this.plugin.removePoiGroup(id);
+ // });
+
+ // this.activePoiGroupIds = [];
+ // }
+
+ // /**
+ // * Add the group of poi to the map
+ // *
+ // * @param options Options of the group of poi to add to the map
+ // * @param state State of the active building to connect with added the group of poi
+ // */
+ // public async addPoiGroup(options: PoiGroupOptions, state?: BuildingState) {
+ // this.poiGroups.add(options, state);
+ // }
+
+ // /**
+ // * Remove the group of poi from the map
+ // *
+ // * @param id Identifier of the group of poi to remove
+ // */
+ // public removePoiGroup(id: Id) {
+ // this.poiGroups.remove(id);
+ // }
+
+ private switchOffGroundCovering() {
+ const attrs = { ...this.groundCoveringSource.getAttributes() };
+ delete attrs['color'];
+ this.groundCoveringSource.setAttributes(attrs);
+ }
+
+ private switchOnGroundCovering() {
+ this.groundCoveringSource.setAttributes({
+ ...this.groundCoveringSource.getAttributes(),
+ color: this.options.groundCoveringColor,
+ });
+ }
+}
+
+function getBuildingModelOptions(building: BuildingOptionsInternal): ModelOptions {
+ return {
+ modelId: building.modelId,
+ coordinates: building.coordinates,
+ modelUrl: building.modelUrl,
+ rotateX: building.rotateX,
+ rotateY: building.rotateY,
+ rotateZ: building.rotateZ,
+ offsetX: building.offsetX,
+ offsetY: building.offsetY,
+ offsetZ: building.offsetZ,
+ scale: building.scale,
+ linkedIds: building.linkedIds,
+ interactive: building.interactive,
+ };
+}
+
+function getFloorModelOptions({
+ buildingOptions,
+ id,
+ modelUrl,
+}: BuildingFloorOptionsInternal): ModelOptions {
+ return {
+ modelId: getFloorModelId(buildingOptions.modelId, id),
+ coordinates: buildingOptions.coordinates,
+ modelUrl,
+ rotateX: buildingOptions.rotateX,
+ rotateY: buildingOptions.rotateY,
+ rotateZ: buildingOptions.rotateZ,
+ offsetX: buildingOptions.offsetX,
+ offsetY: buildingOptions.offsetY,
+ offsetZ: buildingOptions.offsetZ,
+ scale: buildingOptions.scale,
+ linkedIds: buildingOptions.linkedIds,
+ interactive: buildingOptions.interactive,
+ };
+}
+
+function getFloorModelId(buildingModelId: string, floorId: string) {
+ return `${buildingModelId}_${floorId}`;
+}
+
+const getPopupHtml = ({ description, title }: PopupOptions) =>
+ ``;
+
+function isObject(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
diff --git a/src/types/events.ts b/src/types/events.ts
index 2d68965..6db4834 100644
--- a/src/types/events.ts
+++ b/src/types/events.ts
@@ -29,14 +29,9 @@ export interface ModelTarget {
data: ModelOptions;
/**
- * Identifier of the building's model
+ * Identifier of the building's or floor's model
*/
- modelId?: Id;
-
- /**
- * Identifier of the current floor
- */
- floorId?: Id;
+ modelId: Id;
}
export interface PoiTarget {
diff --git a/src/types/plugin.ts b/src/types/plugin.ts
index f389391..9dacf36 100644
--- a/src/types/plugin.ts
+++ b/src/types/plugin.ts
@@ -1,4 +1,4 @@
-export type Id = string | number;
+export type Id = string;
export type ColorModelString = `${'rgb' | 'hsl'}(${string})`;
export type HexColorString = `#${string}`;
@@ -110,12 +110,12 @@ export interface BuildingState {
/**
* Identifier of the building's model
*/
- modelId: Id;
+ buildingId: string;
/**
* Identifier of the floor's model
*/
- floorId?: Id;
+ floorId?: string;
}
/**
diff --git a/src/utils/common.ts b/src/utils/common.ts
index f5b4c55..c9f3f74 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -4,17 +4,6 @@ export function clamp(value: number, min: number, max: number): number {
return value;
}
-export function clone(obj: T): T {
- return JSON.parse(JSON.stringify(obj));
-}
-
-export function createCompoundId(modelId: string | number, floorId?: string | number) {
- if (floorId === undefined) {
- return String(modelId);
- }
- return `${modelId}_${floorId}`;
-}
-
export type RequiredExcept = T & Required>;
type RequiredOptional = Exclude<
diff --git a/src/utils/events.ts b/src/utils/events.ts
new file mode 100644
index 0000000..acab555
--- /dev/null
+++ b/src/utils/events.ts
@@ -0,0 +1,16 @@
+import { MapPointerEvent } from '@2gis/mapgl/types';
+import { GltfPluginModelEvent, Id, ModelOptions, ModelTarget } from '../types';
+
+export const createModelEventData = (
+ ev: MapPointerEvent,
+ data: ModelOptions,
+): GltfPluginModelEvent => ({
+ originalEvent: ev.originalEvent,
+ point: ev.point,
+ lngLat: ev.lngLat,
+ target: {
+ type: 'model',
+ modelId: data.modelId,
+ data,
+ },
+});