@@ -23,7 +22,7 @@ const content = /* HTML */ `
`;
/**
- * A control for change floor layer level on the plugin.
+ * A control for change floor layer level in the plugin.
* It appears on the map only if you set the `floorControl` option within @type PluginOptions to `true`.
* @hidden
* @internal
@@ -120,7 +119,7 @@ export class GltfFloorControl extends Control {
});
}
- private _controlHandler = (modelId: Id) => () => {
+ private _controlHandler = (modelId: string) => () => {
this._switchCurrentFloorLevel(modelId);
this.emit('floorchange', {
@@ -128,7 +127,7 @@ export class GltfFloorControl extends Control {
});
};
- private _switchCurrentFloorLevel(modelId: Id) {
+ private _switchCurrentFloorLevel(modelId: string) {
if (this._currentFloorId === undefined) {
return;
}
diff --git a/src/control/types.ts b/src/control/types.ts
index 453ce22..a7803c4 100644
--- a/src/control/types.ts
+++ b/src/control/types.ts
@@ -1,33 +1,31 @@
-import { Id } from '../types/plugin';
-
/**
- * Floor level data
+ * Floor level data.
*/
export interface FloorLevel {
- modelId: Id; // id модели этажа или здания
+ modelId: string; // id модели этажа или здания
text: string;
icon?: 'parking' | 'building' | string;
}
/**
- * Options for the method show
+ * Options for the show method.
*/
export interface ControlShowOptions {
- buildingModelId: Id;
- activeModelId: Id;
+ buildingModelId: string;
+ activeModelId: string;
floorLevels?: FloorLevel[];
}
/**
- * Event that emitted on button presses of the control
+ * Event that emitted on button presses of the control.
*/
export interface FloorChangeEvent {
- modelId: Id; // id модели этажа или здания
+ modelId: string; // id модели этажа или здания
}
export interface ControlEventTable {
/**
- * Emitted when floor's plan was changed
+ * Emitted when floor's plan was changed.
*/
floorchange: FloorChangeEvent;
}
diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts
index e0383e7..4f74b82 100644
--- a/src/defaultOptions.ts
+++ b/src/defaultOptions.ts
@@ -1,24 +1,14 @@
import type { PluginOptions } from './types/plugin';
export const defaultOptions: Required = {
- hoverHighlight: {
+ hoverOptions: {
color: '#ffffff',
- intencity: 0.0,
},
modelsBaseUrl: '',
modelsLoadStrategy: 'waitAll',
- poiConfig: {
- primary: {
- fontSize: 14,
- fontColor: '#000000',
- },
- secondary: {
- fontSize: 14,
- fontColor: '#000000',
- },
- },
floorsControl: {
position: 'centerLeft',
},
groundCoveringColor: '#F8F8EBCC',
+ zIndex: 0,
};
diff --git a/src/external/evented.ts b/src/external/evented.ts
index e246973..7dd8538 100644
--- a/src/external/evented.ts
+++ b/src/external/evented.ts
@@ -1,5 +1,5 @@
/**
- * Event emitter
+ * Event emitter.
*/
export class Evented {
private events: { [K in keyof M]?: Array<(ev: M[K]) => void> };
@@ -12,9 +12,10 @@ export class Evented {
}
/**
- * Registers event listener
- * @param type Event type
- * @param listener Event handler
+ * Registers event listener.
+ *
+ * @param type Event type.
+ * @param listener Event handler.
*/
public on(type: K, listener: (ev: M[K]) => void): this {
let eventsByType = this.events[type];
@@ -26,9 +27,10 @@ export class Evented {
}
/**
- * Registers event listener which will be called once
- * @param type Event type
- * @param listener Event handler
+ * Registers event listener which will be called once.
+ *
+ * @param type Event type.
+ * @param listener Event handler.
*/
public once(type: K, listener: (ev: M[K]) => void): this {
const wrapper = (data: M[K]) => {
@@ -42,9 +44,10 @@ export class Evented {
}
/**
- * Removes event listener registered with `on`
- * @param type Event type
- * @param listener Event handler
+ * Removes event listener registered with `on`.
+ *
+ * @param type Event type.
+ * @param listener Event handler.
*/
public off(type: K, listener: (ev: M[K]) => void): this {
const eventsByType = this.events[type];
@@ -63,9 +66,10 @@ export class Evented {
}
/**
- * Calls all event listeners with event type `type`
- * @param type Event type
- * @param data Data transferred to events
+ * Calls all event listeners with event type `type`.
+ *
+ * @param type Event type.
+ * @param data Data transferred to events.
*/
public emit(type: K, data?: M[K]): this {
const eventsByType = this.events[type];
diff --git a/src/labelGroups.ts b/src/labelGroups.ts
new file mode 100644
index 0000000..da8923b
--- /dev/null
+++ b/src/labelGroups.ts
@@ -0,0 +1,73 @@
+import type { Map as MapGL, Label, LabelImage } from '@2gis/mapgl/types';
+import type { BuildingState, LabelGroupOptions, PluginOptions } from './types/plugin';
+import type { GltfPlugin } from './plugin';
+// import { pluginEvents } from './constants';
+// import { createLabelEvenData } from './utils/events';
+
+const DEFAULT_IMAGE: LabelImage = {
+ url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHJ4PSI0IiBmaWxsPSIjZWFlYWVhIi8+PHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjI2IiBoZWlnaHQ9IjI2IiByeD0iMyIgZmlsbD0id2hpdGUiLz48L3N2Zz4=',
+ size: [38, 38],
+ stretchX: [[4, 24]],
+ stretchY: [[4, 24]],
+ padding: [5, 10, 5, 10],
+};
+
+export class LabelGroups {
+ private labelsByGroupId: Map = new Map();
+
+ constructor(
+ private map: MapGL,
+ private plugin: GltfPlugin,
+ private options: Required,
+ ) {}
+
+ public add(groupOptions: LabelGroupOptions, state?: BuildingState) {
+ const { id } = groupOptions;
+ if (this.labelsByGroupId.has(id)) {
+ console.error(
+ `Poi group with id "${id}" already exists. Please use different identifiers for poi groups`,
+ );
+ return;
+ }
+
+ const { image, minZoom, maxZoom, fontColor: color, fontSize } = groupOptions;
+ const labels = groupOptions.labels.map((labelOptions) => {
+ const { coordinates, text, userData } = labelOptions;
+ const label = new mapgl.Label(this.map, {
+ coordinates, // + label.elevation ?? groupOptions.elevation
+ text,
+ userData,
+ image: image === 'default' ? DEFAULT_IMAGE : image,
+ minZoom,
+ maxZoom,
+ color,
+ fontSize,
+ relativeAnchor: [0.5, 1],
+ zIndex: this.options.zIndex + 0.00001, // чтобы были выше моделей
+ });
+
+ // pluginEvents.forEach((eventType) => {
+ // label.on(eventType, (ev) => {
+ // this.plugin.emit(eventType, createLabelEvenData(ev, labelOptions, state));
+ // });
+ // });
+
+ return label;
+ });
+
+ this.labelsByGroupId.set(id, labels);
+ }
+
+ public remove(id: string) {
+ const labels = this.labelsByGroupId.get(id);
+ this.labelsByGroupId.delete(id);
+ labels?.forEach((label) => label.destroy());
+ }
+
+ public destroy() {
+ this.labelsByGroupId.forEach((labels) => {
+ labels.forEach((label) => label.destroy());
+ });
+ this.labelsByGroupId.clear();
+ }
+}
diff --git a/src/plugin.ts b/src/plugin.ts
index bcf96f8..cc4859f 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -1,7 +1,7 @@
-import type { Map as MapGL } from '@2gis/mapgl/types';
+import type { DynamicObjectEventTable, GltfModel, Map as MapGL } from '@2gis/mapgl/types';
import type { BuildingOptions } from './types/realtyScene';
import type { GltfPluginEventTable } from './types/events';
-import type { Id, PluginOptions, ModelOptions, BuildingState } from './types/plugin';
+import type { PluginOptions, ModelOptions, BuildingState, LabelGroupOptions } from './types/plugin';
import { applyOptionalDefaults } from './utils/common';
import { Evented } from './external/evented';
@@ -9,11 +9,14 @@ 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';
+import { ModelStatus } from './types/plugin';
+import { pluginEvents } from './constants';
+import { LabelGroups } from './labelGroups';
interface Model {
- instance: any; // GltfModel
+ instance: GltfModel;
options: ModelOptions;
+ isLoaded: boolean;
}
const MODEL_DEFAULTS = {
@@ -27,17 +30,19 @@ const MODEL_DEFAULTS = {
export class GltfPlugin extends Evented {
private map: MapGL;
private options: Required;
- private models: Map;
+ private models: Map;
+ private labelGroups: LabelGroups;
private realtyScene?: RealtyScene;
+ private isDestroyed = false;
/**
- * The main class of the plugin
+ * The main class of the plugin.
*
* Example:
* ```js
- * const plugin = new GltfPlugin (map, {
+ * const plugin = new GltfPlugin(map, {
* modelsLoadStrategy: 'waitAll',
- * ambientLight: { color: 'white', intencity: 2.5 },
+ * modelsBaseUrl: 'https://url_to_models',
* });
*
* plugin.addModels([
@@ -50,8 +55,8 @@ export class GltfPlugin extends Evented {
* },
* ]);
* ```
- * @param map The map instance
- * @param pluginOptions GltfPlugin initialization options
+ * @param map The map instance.
+ * @param pluginOptions GltfPlugin initialization options.
*/
constructor(map: MapGL, pluginOptions?: PluginOptions) {
super();
@@ -59,15 +64,27 @@ export class GltfPlugin extends Evented {
this.map = map;
this.options = applyOptionalDefaults(pluginOptions ?? {}, defaultOptions);
this.models = new Map();
+ this.labelGroups = new LabelGroups(this.map, this, this.options);
+ }
- map.on('styleload', () => {
- this.map.addLayer(GROUND_COVERING_LAYER); // мб унести отсюда в RealtyScene, нужно подумать
- // this.poiGroups.onMapStyleUpdate();
+ /**
+ * Destroys the plugin.
+ */
+ public destroy() {
+ this.isDestroyed = true;
+ this.models.forEach((model) => {
+ model.instance.destroy();
});
+ this.models.clear();
+ this.labelGroups.destroy();
+ this.realtyScene?.destroy();
}
- // public destroy() {}
-
+ /**
+ * Sets options of the plugin.
+ *
+ * @param pluginOptions Plugin options that are available for setting.
+ */
public setOptions(pluginOptions: Pick, 'groundCoveringColor'>) {
Object.keys(pluginOptions).forEach((option) => {
switch (option) {
@@ -80,11 +97,24 @@ export class GltfPlugin extends Evented {
});
}
+ /**
+ * Adds a model to the map.
+ *
+ * @param modelToLoad Options of a model.
+ * @param hideOnLoad Set to `true` if a model should be hidden on loading completion.
+ */
public async addModel(modelToLoad: ModelOptions, hideOnLoad = false) {
return this.addModels([modelToLoad], hideOnLoad ? [] : [modelToLoad.modelId]);
}
- public async addModels(modelsToLoad: ModelOptions[], modelIdsToShow?: Id[]) {
+ /**
+ * Adds a list of models to the map.
+ *
+ * @param modelsToLoad An array of options of models.
+ * @param modelIdsToShow An array of ids of models that should be shown. If it's not provided
+ * all models will be shown.
+ */
+ public async addModels(modelsToLoad: ModelOptions[], modelIdsToShow?: string[]) {
const loadingModels = modelsToLoad
.filter((options) => {
if (this.models.has(options.modelId)) {
@@ -121,28 +151,39 @@ export class GltfPlugin extends Evented {
hideOnInit:
this.options.modelsLoadStrategy === 'waitAll' ||
(modelIdsToShow && !modelIdsToShow.includes(options.modelId)),
+ hover: {
+ color: this.options.hoverOptions.color,
+ },
+ disableAnimation: true,
+ zIndex: this.options.zIndex,
});
- const model = {
+ const model: Model = {
options,
instance,
+ isLoaded: false,
};
this.models.set(options.modelId, model);
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));
- });
- },
- );
+ instance.once('modelloaded' as keyof DynamicObjectEventTable, () => {
+ model.isLoaded = true;
+ resolve(model);
+ });
+ pluginEvents.forEach((eventType) => {
+ instance.on(eventType, (ev) => {
+ this.emit(eventType, createModelEventData(ev, options));
+ });
+ });
});
});
return Promise.all(loadingModels).then((loadedModels) => {
+ if (this.isDestroyed) {
+ return;
+ }
+
if (this.options.modelsLoadStrategy !== 'waitAll') {
return;
}
@@ -155,11 +196,27 @@ export class GltfPlugin extends Evented {
});
}
- public isModelAdded(id: Id) {
- return this.models.has(id);
+ /**
+ * Returns a current status of a model.
+ * There can be no model or it can be loading or loaded.
+ *
+ * @param id A model id.
+ */
+ public getModelStatus(id: string) {
+ const model = this.models.get(id);
+ if (!model) {
+ return ModelStatus.NoModel;
+ }
+
+ return !model.isLoaded ? ModelStatus.Loading : ModelStatus.Loaded;
}
- public removeModel(id: Id) {
+ /**
+ * Removes a model from the map.
+ *
+ * @param id A model id.
+ */
+ public removeModel(id: string) {
const model = this.models.get(id);
if (model) {
model.instance.destroy();
@@ -167,35 +224,84 @@ export class GltfPlugin extends Evented {
}
}
- public removeModels(ids: Id[]) {
+ /**
+ * Removes models from the map.
+ *
+ * @param id Model ids.
+ */
+ public removeModels(ids: string[]) {
ids.forEach((id) => this.removeModel(id));
}
- public showModel(id: Id) {
+ /**
+ * Shows a model on the map.
+ *
+ * @param id A model id.
+ */
+ public showModel(id: string) {
this.models.get(id)?.instance.show();
}
- public showModels(ids: Id[]) {
+ /**
+ * Shows models on the map.
+ *
+ * @param id Model ids.
+ */
+ public showModels(ids: string[]) {
ids.forEach((id) => this.showModel(id));
}
- public hideModel(id: Id) {
+ /**
+ * Hides a model on the map.
+ *
+ * @param id A model id.
+ */
+ public hideModel(id: string) {
this.models.get(id)?.instance.hide();
}
- public hideModels(ids: Id[]) {
+ /**
+ * Hides models on the map.
+ *
+ * @param id Model ids.
+ */
+ public hideModels(ids: string[]) {
ids.forEach((id) => this.hideModel(id));
}
+ /**
+ * Adds a group of labels to the map.
+ *
+ * @param options Options of the group of labels.
+ * @param state A state of active building and floor a group of labels is associated with.
+ */
+ public addLabelGroup(options: LabelGroupOptions, state?: BuildingState) {
+ this.labelGroups.add(options, state);
+ }
+
+ /**
+ * Removes a group of labels from the map.
+ *
+ * @param id A label group id.
+ */
+ public removeLabelGroup(id: string) {
+ this.labelGroups.remove(id);
+ }
+
+ /**
+ * Adds an interactive realty scene to the map.
+ *
+ * @param scene Options of the scene to add to the map.
+ * @param state A state of building and floor that should be active on realty scene initialization.
+ */
public async addRealtyScene(scene: BuildingOptions[], state?: BuildingState) {
this.realtyScene = new RealtyScene(this, this.map, this.options);
return this.realtyScene.init(scene, state);
}
- // public showRealtyScene() {}
-
- // public hideRealtyScene() {}
-
+ /**
+ * Removes an interactive realty scene from the map.
+ */
public removeRealtyScene() {
this.realtyScene?.destroy();
this.realtyScene = undefined;
diff --git a/src/poiGroups.ts b/src/poiGroups.ts
deleted file mode 100644
index 0f69cb5..0000000
--- a/src/poiGroups.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import type { FeatureCollection, Feature, Point } from 'geojson';
-import type { Map as MapGL, GeoJsonSource } from '@2gis/mapgl/types';
-
-import type { Id, PluginOptions, BuildingState, PoiGroupOptions, PoiOptions } from './types/plugin';
-import type { PoiGeoJsonProperties } from './types/events';
-
-type FeaturePoint = Feature;
-
-export class PoiGroups {
- private poiSources = new Map();
- private activePoiGroupOptions?: PoiGroupOptions;
-
- constructor(private map: MapGL, private poiConfig: PluginOptions['poiConfig']) {}
-
- public onMapStyleUpdate() {
- this.map.addIcon('km_pillar_gray_border', {
- url: 'https://disk.2gis.com/styles/d7e8aed1-4d3f-472a-a1e4-337f4b31ab8a/km_pillar_gray_border',
- // @ts-ignore
- width: 38,
- height: 38,
- stretchX: [[4, 24]],
- stretchY: [[4, 24]],
- });
-
- if (this.activePoiGroupOptions) {
- this.addPoiStyleLayer(this.activePoiGroupOptions);
- }
- }
-
- public async add(groupOptions: PoiGroupOptions, state?: BuildingState) {
- this.activePoiGroupOptions = groupOptions;
- const { id, data } = groupOptions;
- const actualId = String(id);
- if (this.poiSources.get(actualId) !== undefined) {
- throw new Error(
- `Poi group with id "${actualId}" already exists. Please use different identifiers for poi groups`,
- );
- }
-
- const geoJson = this.createGeoJson(data, groupOptions, state);
-
- // create source with poi
- const source = new mapgl.GeoJsonSource(this.map, {
- data: geoJson,
- attributes: {
- dataType: actualId,
- },
- });
- this.poiSources.set(actualId, source);
-
- // add style layer for poi
- this.addPoiStyleLayer(groupOptions);
- }
-
- public remove(origId: Id) {
- this.activePoiGroupOptions = undefined;
- const id = String(origId);
- const source = this.poiSources.get(id);
- this.poiSources.delete(id);
- source?.destroy();
- this.map.removeLayer('plugin-poi-' + id);
- }
-
- private createGeoJson(
- poiOptions: PoiOptions[],
- groupOptions: PoiGroupOptions,
- state?: BuildingState,
- ): FeatureCollection {
- const { elevation } = groupOptions;
- const features: FeaturePoint[] = poiOptions.map((opts) => ({
- type: 'Feature',
- properties: {
- // main properties
- type: 'immersive_poi',
- label: opts.label,
- userData: opts.userData,
- elevation: elevation,
- coordinates: opts.coordinates,
- // auxilary properties
- modelId: state?.modelId,
- floorId: state?.floorId,
- },
- geometry: {
- type: 'Point',
- coordinates: opts.coordinates,
- },
- }));
-
- return {
- type: 'FeatureCollection',
- features,
- };
- }
-
- private addPoiStyleLayer(groupOptions: PoiGroupOptions) {
- const { id, type, minZoom = -Infinity, maxZoom = +Infinity } = groupOptions;
- let { fontSize, fontColor } = groupOptions;
- const actualId = String(id);
- let style;
-
- if (fontColor === undefined) {
- fontColor =
- type === 'primary'
- ? this.poiConfig?.primary?.fontColor ?? '#3a3a3a'
- : this.poiConfig?.secondary?.fontColor ?? '#3a3a3a';
- }
- if (fontSize === undefined) {
- fontSize =
- type === 'primary'
- ? this.poiConfig?.primary?.fontSize ?? 14
- : this.poiConfig?.secondary?.fontSize ?? 12;
- }
-
- if (type === 'primary') {
- style = {
- iconPriority: 7000,
- allowElevation: true,
- elevation: ['get', 'elevation'],
- iconImage: 'km_pillar_gray_border',
- iconAnchor: [0.5, 1],
- iconOffset: [0, 0],
- iconTextFont: 'Noto_Sans',
- iconTextColor: fontColor,
- iconTextField: ['get', 'label'],
- iconTextPadding: [5, 10, 5, 10],
- iconTextFontSize: fontSize,
- duplicationSpacing: 1,
- };
- } else {
- style = {
- allowElevation: true,
- elevation: ['get', 'elevation'],
- duplicationSpacing: 1,
- textField: ['get', 'label'],
- textFont: 'Noto_Sans',
- textFontSize: fontSize,
- textColor: fontColor,
- textPriority: 6000,
- };
- }
-
- this.map.addLayer({
- type: 'point',
- id: 'plugin-poi-' + actualId,
- filter: [
- 'all',
- ['match', ['sourceAttr', 'dataType'], [actualId], true, false],
- ['match', ['get', 'type'], ['immersive_poi'], true, false],
- ],
- style,
- minzoom: minZoom,
- maxzoom: maxZoom,
- });
- }
-}
diff --git a/src/realtyScene/realtyScene.ts b/src/realtyScene/realtyScene.ts
index f21f62f..169cadc 100644
--- a/src/realtyScene/realtyScene.ts
+++ b/src/realtyScene/realtyScene.ts
@@ -4,46 +4,50 @@ import { GltfPlugin } from '../plugin';
import { GltfFloorControl } from '../control';
import classes from './realtyScene.module.css';
-import type { BuildingState, Id, ModelOptions, PluginOptions } from '../types/plugin';
+import {
+ ModelStatus,
+ type BuildingState,
+ type ModelOptions,
+ type PluginOptions,
+} from '../types/plugin';
import type {
BuildingOptions,
MapOptions,
- BuildingFloorOptions,
PopupOptions,
+ BuildingOptionsInternal,
+ BuildingFloorOptionsInternal,
+ RealtySceneState,
} 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;
-};
+import type { FloorChangeEvent } from '../control/types';
+import type { GltfPluginModelEvent, GltfPluginLabelEvent } from '../types/events';
+import {
+ GROUND_COVERING_SOURCE_DATA,
+ GROUND_COVERING_SOURCE_PURPOSE,
+ GROUND_COVERING_LAYER,
+ GROUND_COVERING_LAYER_ID,
+} from '../constants';
+import {
+ getBuildingModelOptions,
+ getFloorModelId,
+ getFloorModelOptions,
+ getFloorPoiGroupId,
+ isObject,
+} from '../utils/realtyScene';
export class RealtyScene {
- private buildings = new Map();
- private floors = new Map();
- private undergroundFloors = new Set();
+ private buildings = new Map();
+ private floors = new Map();
+ private undergroundFloors = new Set();
private state: RealtySceneState = {
activeModelId: undefined,
buildingVisibility: new Map(),
};
+ private isDestroyed = false;
private groundCoveringSource: GeoJsonSource;
private control: GltfFloorControl;
private popup?: HtmlMarker;
- // private poiGroups: PoiGroups;
-
constructor(
private plugin: GltfPlugin,
private map: MapGL,
@@ -51,7 +55,6 @@ export class RealtyScene {
) {
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,
@@ -59,9 +62,12 @@ export class RealtyScene {
purpose: GROUND_COVERING_SOURCE_PURPOSE,
},
});
+
+ this.map.addLayer(GROUND_COVERING_LAYER);
+ map.on('styleload', this.onStyleLoad);
}
- private getBuildingModelId(id: Id | undefined) {
+ private getBuildingModelId(id: string | undefined) {
if (id === undefined) {
return;
}
@@ -79,44 +85,109 @@ export class RealtyScene {
private setState(newState: RealtySceneState) {
const prevState = this.state;
+ // т.к. стейт может меняться асинхронно и иногда нужно показывать
+ // предыдущую модель некоторое время, реальный стейт заполняется тут,
+ // а выставление нужного будет отложено на время загрузки модели
+ const buildingVisibility: Map = new Map();
+
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 (prevModelOptions?.modelId === newModelOptions?.modelId) {
+ buildingVisibility.set(buildingId, prevModelOptions);
+ return;
}
- 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 (prevModelOptions) {
+ // если нужно отобразить подземный этаж, но его модель не готова, то ничего не скрываем
+ if (
+ !newModelOptions &&
+ newState.activeModelId !== undefined &&
+ this.undergroundFloors.has(newState.activeModelId) &&
+ this.plugin.getModelStatus(newState.activeModelId) !== ModelStatus.Loaded
+ ) {
+ buildingVisibility.set(buildingId, prevModelOptions);
+ } else if (
+ // если новая модель готова или предыдущую нужно просто скрыть, то скрываем ее
+ !newModelOptions ||
+ this.plugin.getModelStatus(newModelOptions.modelId) === ModelStatus.Loaded
+ ) {
+ this.plugin.hideModel(prevModelOptions.modelId);
+ buildingVisibility.set(buildingId, undefined);
+
+ if (this.undergroundFloors.has(prevModelOptions.modelId)) {
+ this.switchOffGroundCovering();
+ }
+
+ const floorOptions = this.floors.get(prevModelOptions.modelId);
+ if (floorOptions) {
+ floorOptions.labelGroups?.forEach((group) => {
+ this.plugin.removeLabelGroup(group.id);
+ });
+ }
}
+ }
- if (this.undergroundFloors.has(newState.activeModelId)) {
- this.switchOnGroundCovering();
+ if (newModelOptions) {
+ const modelStatus = this.plugin.getModelStatus(newModelOptions.modelId);
+
+ // если новая модель готова, то показываем ее
+ if (modelStatus === ModelStatus.Loaded) {
+ this.plugin.showModel(newModelOptions.modelId);
+ buildingVisibility.set(buildingId, newModelOptions);
+
+ // если модель активна, то применяем опции карты и включаем подложку, если нужно
+ if (
+ newState.activeModelId !== undefined &&
+ newState.activeModelId === newModelOptions.modelId
+ ) {
+ const options =
+ this.buildings.get(newModelOptions.modelId) ??
+ this.floors.get(newModelOptions.modelId);
+
+ if (options) {
+ this.setMapOptions(options.mapOptions);
+ }
+
+ if (this.undergroundFloors.has(newModelOptions.modelId)) {
+ this.switchOnGroundCovering();
+ }
+
+ const floorOptions = this.floors.get(newModelOptions.modelId);
+ if (floorOptions) {
+ floorOptions.labelGroups?.forEach((group) => {
+ this.plugin.addLabelGroup(group, {
+ buildingId,
+ floorId: floorOptions.id,
+ });
+ });
+ }
+ }
+ } else {
+ if (modelStatus === ModelStatus.NoModel) {
+ this.plugin.addModel(newModelOptions, true).then(() => {
+ if (this.isDestroyed) {
+ return;
+ }
+
+ if (this.state.activeModelId !== newModelOptions.modelId) {
+ return;
+ }
+
+ // откладываем выставление нужного стейта до момента загрузки модели
+ this.setState(newState);
+ });
+ }
+
+ // если новые модели не готовы, то пока показываем предыдущие
+ buildingVisibility.set(buildingId, prevModelOptions);
}
}
- }
+ });
+ // контрол реагирует на изменения стейта сразу, без учета загрузки модели, т.к. завязан на здание в целом
const prevBuildingModelId = this.getBuildingModelId(prevState.activeModelId);
const newBuildingModelId = this.getBuildingModelId(newState.activeModelId);
@@ -140,12 +211,15 @@ export class RealtyScene {
}
}
- this.state = newState;
+ this.state = {
+ buildingVisibility,
+ activeModelId: newState.activeModelId,
+ };
}
public async init(scene: BuildingOptions[], state?: BuildingState) {
// Приводим стейт пользователя к внутреннему виду id
- let activeModelId: Id | undefined = state
+ let activeModelId: string | undefined = state
? state.floorId
? getFloorModelId(state.buildingId, state.floorId)
: state.buildingId
@@ -169,6 +243,10 @@ export class RealtyScene {
this.floors.set(floorModelId, {
...floor,
+ labelGroups: (floor.labelGroups ?? []).map((group) => ({
+ ...group,
+ id: getFloorPoiGroupId(building.modelId, floor.id, group.id),
+ })),
buildingOptions: buildingOptions,
});
@@ -187,8 +265,8 @@ export class RealtyScene {
? activeModelId
: undefined;
- const modelsToLoad: Map = new Map();
- const buildingVisibility: Map = new Map();
+ const modelsToLoad: Map = new Map();
+ const buildingVisibility: Map = new Map();
this.buildings.forEach((options, id) => {
const modelOptions = getBuildingModelOptions(options);
@@ -198,6 +276,7 @@ export class RealtyScene {
if (activeModelId) {
const floorOptions = this.floors.get(activeModelId);
+
if (floorOptions) {
if (this.undergroundFloors.has(activeModelId)) {
buildingVisibility.clear(); // показываем только подземный этаж
@@ -218,6 +297,10 @@ export class RealtyScene {
return this.plugin
.addModels(Array.from(modelsToLoad.values()), Array.from(buildingVisibility.keys()))
.then(() => {
+ if (this.isDestroyed) {
+ return;
+ }
+
this.setState({
activeModelId,
buildingVisibility,
@@ -241,14 +324,20 @@ export class RealtyScene {
}
public destroy() {
+ this.isDestroyed = true;
+ this.map.off('styleload', this.onStyleLoad);
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.floors.forEach(({ labelGroups }) => {
+ labelGroups?.forEach(({ id }) => {
+ this.plugin.removeLabelGroup(id);
+ });
+ });
this.plugin.removeModels([...this.buildings.keys(), ...this.floors.keys()]);
-
- // this.clearPoiGroups();
+ this.map.removeLayer(GROUND_COVERING_LAYER_ID);
this.groundCoveringSource.destroy();
this.undergroundFloors.clear();
@@ -288,7 +377,11 @@ export class RealtyScene {
}
}
- private onSceneMouseOut = (ev: GltfPluginPoiEvent | GltfPluginModelEvent) => {
+ private onStyleLoad = () => {
+ this.map.addLayer(GROUND_COVERING_LAYER);
+ };
+
+ private onSceneMouseOut = (ev: GltfPluginLabelEvent | GltfPluginModelEvent) => {
if (ev.target.type !== 'model') {
return;
}
@@ -296,8 +389,8 @@ export class RealtyScene {
this.popup?.destroy();
};
- private onSceneMouseOver = ({ target }: GltfPluginPoiEvent | GltfPluginModelEvent) => {
- if (target.type === 'poi' || target.modelId === undefined) {
+ private onSceneMouseOver = ({ target }: GltfPluginLabelEvent | GltfPluginModelEvent) => {
+ if (target.type === 'label' || target.modelId === undefined) {
return;
}
@@ -313,13 +406,13 @@ export class RealtyScene {
});
};
- private onSceneClick = ({ target }: GltfPluginPoiEvent | GltfPluginModelEvent) => {
+ private onSceneClick = ({ target }: GltfPluginLabelEvent | GltfPluginModelEvent) => {
if (target.type === 'model') {
const options = this.buildings.get(target.modelId);
if (options) {
this.buildingClickHandler(target.modelId);
}
- } else if (target.type === 'poi') {
+ } else if (target.type === 'label') {
const userData = target.data.userData;
if (isObject(userData) && typeof userData.url === 'string') {
const a = document.createElement('a');
@@ -331,7 +424,7 @@ export class RealtyScene {
};
private floorChangeHandler = (ev: FloorChangeEvent) => {
- const buildingVisibility: Map = new Map();
+ const buildingVisibility: Map = new Map();
this.buildings.forEach((options, id) => {
buildingVisibility.set(id, getBuildingModelOptions(options));
});
@@ -361,14 +454,14 @@ export class RealtyScene {
}
};
- private buildingClickHandler = (modelId: Id) => {
+ private buildingClickHandler = (modelId: string) => {
const buildingOptions = this.buildings.get(modelId);
if (!buildingOptions) {
return;
}
let activeModelId = modelId;
- const buildingVisibility: Map = new Map();
+ const buildingVisibility: Map = new Map();
this.buildings.forEach((options, id) => {
buildingVisibility.set(id, getBuildingModelOptions(options));
});
@@ -396,55 +489,6 @@ export class RealtyScene {
});
};
- // 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'];
@@ -459,54 +503,8 @@ export class RealtyScene {
}
}
-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) =>
`
${title}
${description ? `
${description}
` : ''}
`;
-
-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 6db4834..4c3aa71 100644
--- a/src/types/events.ts
+++ b/src/types/events.ts
@@ -1,119 +1,102 @@
-import type { Id, ModelOptions, PoiOptions } from './plugin';
-
-export type PoiGeoJsonProperties = PoiOptions & {
- /**
- * Identifier of the building's model
- */
- modelId?: Id;
-
- /**
- * Identifier of the floor's model
- */
- floorId?: Id;
-
- /**
- * Type of the poi
- */
- type?: string;
-};
+import type { ModelOptions, LabelOptions } from './plugin';
export interface ModelTarget {
/**
- * Type of the target
+ * The type of a target.
*/
type: 'model';
/**
- * The targeted model
+ * A targeted model.
*/
data: ModelOptions;
/**
- * Identifier of the building's or floor's model
+ * An identifier of the building's or floor's model.
*/
- modelId: Id;
+ modelId: string;
}
-export interface PoiTarget {
+export interface LabelTarget {
/**
- * Type of the target
+ * The type of a target.
*/
- type: 'poi';
+ type: 'label';
/**
- * The targeted poi
+ * A targeted label.
*/
- data: PoiGeoJsonProperties;
+ data: LabelOptions;
/**
- * Identifier of the building's model
+ * An identifier of the building's model.
*/
- modelId?: Id;
+ buildingId?: string;
/**
- * Identifier of the current floor
+ * An identifier of the current floor.
*/
- floorId?: Id;
+ floorId?: string;
}
/**
- * The event type for pointer-related plugin events
+ * Event type for pointer-related plugin events.
*/
interface GltfPluginPointerEvent {
/**
- * The original DOM event
+ * An original DOM event.
*/
originalEvent: MouseEvent | TouchEvent;
/**
- * Geographical coordinates of the event
+ * Geographical coordinates of an event.
*/
lngLat: number[];
/**
- * Screen coordinates of the event
+ * Screen coordinates of an event.
*/
point: number[];
}
/**
- * The event type for pointer-related plugin events emitted by the poi
+ * Event type for pointer-related plugin events emitted by a label.
*/
-export interface GltfPluginPoiEvent extends GltfPluginPointerEvent {
+export interface GltfPluginLabelEvent extends GltfPluginPointerEvent {
/**
- * Target of the poi event
+ * A target of a label event.
*/
- target: PoiTarget;
+ target: LabelTarget;
}
/**
- * The event type for pointer-related plugin events emitted by the model
+ * Event type for pointer-related plugin events emitted by a model.
*/
export interface GltfPluginModelEvent extends GltfPluginPointerEvent {
/**
- * Target of the model event
+ * A target of a model event.
*/
target: ModelTarget;
}
/**
- * The list of events that can be emitted by the glTF plugin instance
+ * List of events that can be emitted by the GLTF plugin instance.
*/
export interface GltfPluginEventTable {
/**
- * Emitted when model or poi are clicked
+ * Emitted when a model or a label is clicked.
*/
- click: GltfPluginPoiEvent | GltfPluginModelEvent;
+ click: GltfPluginLabelEvent | GltfPluginModelEvent;
/**
- * Emitted when the user moves the pointer over the model or the poi
+ * Emitted when user moves pointer over a model or a label.
*/
- mousemove: GltfPluginPoiEvent | GltfPluginModelEvent;
+ mousemove: GltfPluginLabelEvent | GltfPluginModelEvent;
/**
- * Emitted when the user hovers over the model or the poi
+ * Emitted when user hovers over a model or a label.
*/
- mouseover: GltfPluginPoiEvent | GltfPluginModelEvent;
+ mouseover: GltfPluginLabelEvent | GltfPluginModelEvent;
/**
- * Emitted when the user moves the mouse away from the model or the poi
+ * Emitted when user moves mouse away from a model or a label.
*/
- mouseout: GltfPluginPoiEvent | GltfPluginModelEvent;
+ mouseout: GltfPluginLabelEvent | GltfPluginModelEvent;
}
diff --git a/src/types/plugin.ts b/src/types/plugin.ts
index 9dacf36..9f45ce7 100644
--- a/src/types/plugin.ts
+++ b/src/types/plugin.ts
@@ -1,26 +1,4 @@
-export type Id = string;
-
-export type ColorModelString = `${'rgb' | 'hsl'}(${string})`;
-export type HexColorString = `#${string}`;
-
-/**
- * Color representation can be rgb(), hsl(), or hex value
- */
-export type ColorRepresentation = ColorModelString | HexColorString | number;
-
-/**
- * Configuration of the poi
- */
-export interface PoiConfigGranular {
- /**
- * Size of the font
- */
- fontSize?: number;
- /**
- * Color of the font
- */
- fontColor?: string;
-}
+import type { LabelImage } from '@2gis/mapgl/types';
/**
* Possible positions of the control.
@@ -40,198 +18,194 @@ export type ControlPosition =
*/
export interface ControlOptions {
/**
- * Position of the control.
+ * A position of the control.
*/
position: ControlPosition;
}
/**
- * Options for the highlight color of hovered models
+ * Options for the hover state of models.
*/
-export interface HightlightOptions {
- // TODO: MAJOR. Rename to «HighlightOptions» in the next major release.
- /**
- * Color of the hover
- * @default '#ffffff'
- */
- color?: ColorRepresentation;
+export interface HoverOptions {
/**
- * Intensity of the color on the hover in the range from 0 to 1
- * @default 0.0
+ * A hover color.
*/
- intencity: number; // TODO: MAJOR. Rename to «intensity» in the next major release.
+ color: string;
}
/**
- * Options for the plugin
+ * Options for the plugin.
*/
export interface PluginOptions {
/**
- * The url which is used for resolving of a model's relative url
+ * A URL which is used for resolving of a model's relative path.
*/
modelsBaseUrl?: string;
/**
- * Strategies for the loading of models:
- * - dontWaitAll - show models as soon as possible
- * - waitAll - show models only when all models are ready for the rendering
+ * Strategies for loading of models:
+ * - dontWaitAll - show models as soon as possible.
+ * - waitAll - show models only when all models are ready for rendering.
*/
modelsLoadStrategy?: 'dontWaitAll' | 'waitAll';
/**
- * Configuration of poi
- */
- poiConfig?: {
- /**
- * Configuration the primary poi
- */
- primary?: PoiConfigGranular;
- /**
- * Configuration the secondary poi
- */
- secondary?: PoiConfigGranular;
- };
- /**
- * Settings for floors' control
+ * Settings for floors' control.
*/
floorsControl?: ControlOptions;
/**
- * Settings of the highlighted models
+ * Settings of hovered models.
*/
- hoverHighlight?: HightlightOptions;
+ hoverOptions?: HoverOptions;
/**
* Color for the ground covering when an underground floor's plan is shown.
*/
groundCoveringColor?: string;
+ /**
+ * Draw order of plugin objects (models and labels).
+ * It may be useful when other map objects (such as markers, shapes, etc.) need to be added
+ * on the map so that user could manage draw order of the plugin and these objects.
+ */
+ zIndex?: number;
}
/**
- * State for the building's scene
+ * State for the building's scene.
*/
export interface BuildingState {
/**
- * Identifier of the building's model
+ * An identifier of the building's model.
*/
buildingId: string;
/**
- * Identifier of the floor's model
+ * An identifier of the floor's model.
*/
floorId?: string;
}
/**
- * Options for a model
+ * Options for a model.
*/
export interface ModelOptions {
/**
- * Identifier of the model should be unique for every model
+ * An identifier of a model should be unique for every model.
*/
- modelId: Id;
+ modelId: string;
/**
- * Geographical coordinates [longitude, latitude]
+ * Geographical coordinates [longitude, latitude].
*/
coordinates: number[];
/**
- * Url where the model is located
+ * URL where a model is located.
*/
modelUrl: string;
/**
- * Rotation of the model in degrees about the X axis
+ * Rotation of a model in degrees about the X axis.
*/
rotateX?: number;
/**
- * Rotation of the model in degrees about the Y axis
+ * Rotation of a model in degrees about the Y axis.
*/
rotateY?: number;
/**
- * Rotation of the model in degrees about the Z axis
+ * Rotation of a model in degrees about the Z axis.
*/
rotateZ?: number;
/**
- * Offset of the model along the X axis in meters
+ * Offset of a model along the X axis in meters.
*/
offsetX?: number;
/**
- * Offset of the model along the Y axis in meters
+ * Offset of a model along the Y axis in meters.
*/
offsetY?: number;
/**
- * Offset of the model along the Z axis in meters
+ * Offset of a model along the Z axis in meters.
*/
offsetZ?: number;
/**
- * Scale of the model
+ * Scale of a model.
*/
scale?: number;
/**
- * List of buildings' identifiers that should be hidden
+ * A list of buildings' identifiers that should be hidden.
*/
linkedIds?: string[];
/**
- * User specific data
+ * User specific data.
*/
userData?: any;
/**
- * Interactivity of model. All models are interactive by default
+ * Interactivity of model. All models are interactive by default.
*/
interactive?: boolean;
}
/**
- * Options for a poi
+ * Options for a label.
*/
-export interface PoiOptions {
+export interface LabelOptions {
/**
- * Coordinate of the poi
+ * Coordinates of a label.
*/
coordinates: [number, number];
/**
- * Elevation of the poi
+ * An elevation of a label.
*/
elevation?: number;
/**
- * Elevation of the poi
+ * A text of a label.
*/
- label: string;
+ text: string;
/**
- * User specific data
+ * User specific data.
*/
userData?: any;
}
/**
- * Options for a poi group
+ * Options for a label group.
*/
-export interface PoiGroupOptions {
+export interface LabelGroupOptions {
/**
- * Identifier of the poi group to add
+ * An identifier of a label group to add.
*/
- id: Id;
+ id: string;
/**
- * Type of the poi
+ * An elevation of a label group.
*/
- type: 'primary' | 'secondary';
+ elevation: number;
/**
- * Elevation of the group of poi
+ * An array of labels to add on the map
*/
- elevation: number;
+ labels: LabelOptions[];
/**
- * Array of poi to add on the map
+ * Image settings for labels' text background.
*/
- data: PoiOptions[];
+ image?: LabelImage | 'default';
/**
- * Minimum display styleZoom of the poi group
+ * A minimum display styleZoom of a label group.
*/
minZoom?: number;
/**
- * Maximum display styleZoom of the poi group
+ * A maximum display styleZoom of a label group.
*/
maxZoom?: number;
/**
- * Size of the poi's font
+ * A size of a label's font.
*/
fontSize?: number;
/**
- * Color of the poi's font
+ * A color of a label's font.
*/
fontColor?: string;
}
+
+/**
+ * Status of a model.
+ * There can be no model or it can be loading or loaded.
+ */
+export enum ModelStatus {
+ NoModel,
+ Loading,
+ Loaded,
+}
diff --git a/src/types/realtyScene.ts b/src/types/realtyScene.ts
index e566032..60c52fd 100644
--- a/src/types/realtyScene.ts
+++ b/src/types/realtyScene.ts
@@ -1,53 +1,54 @@
-import type { Id, PoiGroupOptions, ModelOptions } from './plugin';
+import { FloorLevel } from '../control/types';
+import type { ModelOptions, LabelGroupOptions } from './plugin';
/**
- * Options for the map
+ * Options for the map.
*/
export interface MapOptions {
/**
- * Geographical center of the map
+ * Geographical center of the map.
*/
center?: number[];
/**
- * Map's pitch angle in degrees
+ * Map's pitch angle in degrees.
*/
pitch?: number;
/**
- * Map's rotation angle in degrees
+ * Map's rotation angle in degrees.
*/
rotation?: number;
/**
- * Map's zoom
+ * Map's zoom.
*/
zoom?: number;
}
/**
- * Options for a floor's plan on the realty scene
+ * Options for a floor's plan in the realty scene.
*/
export interface BuildingFloorOptions {
/**
- * Identifier of the floor's plan
+ * An identifier of the floor's plan.
*/
- id: Id;
+ id: string;
/**
- * Text to add to the floors' control
+ * A text to add to the floors' control.
*/
text: string;
/**
- * Url of a model that represents the current floor's plan
+ * A URL of a model that represents the current floor's plan.
*/
modelUrl: string;
/**
- * Icon to add to the floors' control
+ * An icon to add to the floors' control.
*/
icon?: 'building' | 'parking' | string;
/**
- * List of poi groups connected with the floor's plan
+ * A list of groups of labels connected with the floor's plan.
*/
- poiGroups?: PoiGroupOptions[];
+ labelGroups?: LabelGroupOptions[];
/**
- * Map's options to apply after selecting the particular floor
+ * Map's options to apply after selecting the particular floor.
*/
mapOptions?: MapOptions;
/**
@@ -59,37 +60,64 @@ export interface BuildingFloorOptions {
}
/**
- * Options of popup that appears on hover of buildings
+ * Options of popup that appears on hover of buildings.
*/
export interface PopupOptions {
/**
- * Popup's coordinates
+ * Popup's coordinates.
*/
coordinates: number[];
/**
- * Popup's title
+ * A popup's title.
*/
title: string;
/**
- * Popup's description
+ * A popup's description.
*/
description?: string;
}
/**
- * Options for a building on the realty scene
+ * Options for a building in the realty scene.
*/
export interface BuildingOptions extends ModelOptions {
/**
- * Map's options to apply after selecting the particular building
+ * Map's options to apply after selecting the particular building.
*/
mapOptions?: MapOptions;
/**
- * List of the floors' plans connected with the particular building
+ * A list of the floors' plans connected with the particular building.
*/
floors?: BuildingFloorOptions[];
/**
- * Popup options
+ * Popup options.
*/
popupOptions?: PopupOptions;
}
+
+/**
+ * @hidden
+ * @internal
+ */
+export interface RealtySceneState {
+ activeModelId?: string;
+
+ // id здания мапится на опции здания или опции этажа этого здания
+ buildingVisibility: Map;
+}
+
+/**
+ * @hidden
+ * @internal
+ */
+export type BuildingOptionsInternal = Omit & {
+ floors: FloorLevel[];
+};
+
+/**
+ * @hidden
+ * @internal
+ */
+export type BuildingFloorOptionsInternal = BuildingFloorOptions & {
+ buildingOptions: ModelOptions;
+};
diff --git a/src/utils/common.ts b/src/utils/common.ts
index c9f3f74..5fd50dd 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,11 +1,3 @@
-export function clamp(value: number, min: number, max: number): number {
- value = Math.max(value, min);
- value = Math.min(value, max);
- return value;
-}
-
-export type RequiredExcept = T & Required>;
-
type RequiredOptional = Exclude<
{
[K in keyof T]: T extends Record ? never : K;
diff --git a/src/utils/events.ts b/src/utils/events.ts
index acab555..3845889 100644
--- a/src/utils/events.ts
+++ b/src/utils/events.ts
@@ -1,8 +1,15 @@
-import { MapPointerEvent } from '@2gis/mapgl/types';
-import { GltfPluginModelEvent, Id, ModelOptions, ModelTarget } from '../types';
+import type { DynamicObjectPointerEvent, GltfModel, Label } from '@2gis/mapgl/types';
+import {
+ BuildingState,
+ GltfPluginLabelEvent,
+ GltfPluginModelEvent,
+ LabelOptions,
+ LabelTarget,
+ ModelOptions,
+} from '../types';
export const createModelEventData = (
- ev: MapPointerEvent,
+ ev: DynamicObjectPointerEvent,
data: ModelOptions,
): GltfPluginModelEvent => ({
originalEvent: ev.originalEvent,
@@ -14,3 +21,29 @@ export const createModelEventData = (
data,
},
});
+
+export const createLabelEvenData = (
+ ev: DynamicObjectPointerEvent