@itwin/measure-tools-react
Version:
Frontend framework and tools for measurements
728 lines • 33.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { UiFramework } from "@itwin/appui-react";
import { BeButton, BeButtonEvent, IModelApp } from "@itwin/core-frontend";
import { Point3d, Transform } from "@itwin/core-geometry";
import { Point2d } from "@itwin/core-geometry";
import { MeasurementButtonHandledEvent, WellKnownMeasurementStyle, WellKnownViewType } from "./MeasurementEnums.js";
import { MeasurementPreferences } from "./MeasurementPreferences.js";
import { MeasurementViewTarget } from "./MeasurementViewTarget.js";
import { ShimFunctions } from "./ShimFunctions.js";
import { SheetMeasurementsHelper } from "./SheetMeasurementHelper.js";
export var DrawingMetadata;
(function (DrawingMetadata) {
function toJSON(obj) {
if (obj === undefined)
return undefined;
const origin = obj.origin?.toJSONXY();
const extents = obj.extents?.toJSONXY();
const masterOrigin = obj.sheetToWorldTransform?.masterOrigin.toJSONXYZ();
const sheetTov8Drawing = obj.sheetToWorldTransform?.sheetTov8Drawing.toJSON();
const v8DrawingToDesign = obj.sheetToWorldTransform?.v8DrawingToDesign.toJSON();
if (origin !== undefined)
return { origin, extents, worldScale: obj.worldScale, drawingId: obj.drawingId, sheetToWorldTransform: (masterOrigin !== undefined && sheetTov8Drawing !== undefined && v8DrawingToDesign !== undefined) ? { masterOrigin, sheetTov8Drawing, v8DrawingToDesign } : undefined };
return undefined;
}
DrawingMetadata.toJSON = toJSON;
function fromJSON(json) {
return {
origin: Point2d.fromJSON(json.origin),
worldScale: json.worldScale,
drawingId: json.drawingId,
extents: Point2d.fromJSON(json.extents),
sheetToWorldTransform: json.sheetToWorldTransform ? {
masterOrigin: Point3d.fromJSON(json.sheetToWorldTransform?.masterOrigin),
sheetTov8Drawing: Transform.fromJSON(json.sheetToWorldTransform?.sheetTov8Drawing),
v8DrawingToDesign: Transform.fromJSON(json.sheetToWorldTransform?.v8DrawingToDesign),
} : undefined,
};
}
DrawingMetadata.fromJSON = fromJSON;
// Returns a new DrawingMetaData object with one or more properties changed.
function withOverrides(current, overrides) {
return { ...current, ...overrides };
}
DrawingMetadata.withOverrides = withOverrides;
})(DrawingMetadata || (DrawingMetadata = {}));
/** Abstract class for serializers that read/write measurements from JSON. */
export class MeasurementSerializer {
/** Given a JSON object, a single or an array of Measurements is attempted to be parsed.
* @param data JSON object or undefined.
* @returns Single or Multiple measurements or undefined if data could not be parsed correctly.
*/
parse(data) {
if (data === undefined)
return undefined;
const propName = this.measurementName;
const propValue = data[propName];
if (propValue === undefined)
return undefined;
const measurements = [];
const arr = Array.isArray(propValue) ? propValue : [propValue];
for (const elem of arr) {
const elemMeasurement = this.parseSingle(elem);
if (elemMeasurement !== undefined)
measurements.push(elemMeasurement);
}
if (measurements.length === 0)
return undefined;
// Avoid returning arrays of a single measurement...
return (measurements.length === 1) ? measurements[0] : measurements;
}
/** Serializes single or multiple measurements to a JSON object.
* @param measurement Single or multiple measurements.
* @returns JSON object that will have a single property (the measurementName) or undefined if serialization failed.
*/
serialize(measurement) {
if (!this.areValidTypes(measurement))
return undefined;
const propName = this.measurementName;
const jsonArray = [];
const arr = Array.isArray(measurement) ? measurement : [measurement];
for (const elem of arr) {
const jsonValue = this.serializeSingle(elem);
if (jsonValue !== undefined)
jsonArray.push(jsonValue);
}
if (jsonArray.length === 0)
return undefined;
const propValue = (jsonArray.length === 1) ? jsonArray[0] : jsonArray;
return { [propName]: propValue };
}
/** Determines if the measurements are all the same type. Serializers do not handle an array of differently typed measurements.
* @param measurement One or more measurements to check
* @returns true if all measurements are of the same type, false if otherwise.
*/
areValidTypes(measurement) {
const arr = Array.isArray(measurement) ? measurement : [measurement];
if (arr.some((elem) => elem === undefined || !this.isValidType(elem)))
return false;
return true;
}
/**
* Subclasses can implement this to do JSON data validation. Some measurements may have optional properties, other measurements may need data that HAS to be present in order to
* create a new instance.
* @param json JSON data to validate.
*/
isValidJSON(_json) {
// Currently all measurement base props are optional
return true;
}
/** Handles serializing a measurement type to JSON.
* @param measurement Measurement instance to serialize.
* @returns JSON object with measurement props.
*/
serializeSingle(measurement) {
if (this.isValidType(measurement))
return measurement.toJSON();
return undefined;
}
}
/** Defines the context for testing if a measurement is being picked. */
export class MeasurementPickContext {
/**
* Constructs a new pick detail.
* @param geomId Geometry ID that was picked.
* @param hitDetail Optional information about the pick hit on the geometry.
* @param ev Optional information about the button event that generated the pick.
*/
constructor(geomId, hitDetail, ev) {
this.geomId = geomId;
this.hitDetail = hitDetail;
this.buttonEvent = ev;
}
/**
* Creates a pick detail from the supplied arguments.
* @param hitDetail HitDetail containing the pick information.
* @param ev Button event, if undefined the current input state is queried.
*/
static create(hitDetail, ev) {
if (!ev) {
ev = new BeButtonEvent();
IModelApp.toolAdmin.fillEventFromLastDataButton(ev);
}
return new MeasurementPickContext(hitDetail.sourceId, hitDetail, ev);
}
/**
* Creates a pick detail from the supplied arguments.
* @param geomId Geometry ID that was picked
* @param ev Button event, if undefined the current input state is queried.
*/
static createFromSourceId(geomId, ev) {
if (!ev) {
ev = new BeButtonEvent();
IModelApp.toolAdmin.fillEventFromLastDataButton(ev);
}
return new MeasurementPickContext(geomId, undefined, ev);
}
}
/**
* Abstract class representing a Measurement. Measurements are semi-persistent annotation objects that can be drawn to a viewport. They are not stored
* in the iModel database, but can be serialized to a JSON string for storage.
*/
export class Measurement {
/** Gets the serializer associated with this measurement type. If undefined, the measurement does not participate in serialization. */
get serializer() {
return Object.getPrototypeOf(this).constructor.serializer;
}
/** Gets or sets the transient ID used by this measurement. This enables using measurements with selection, snapping, and picking.
* Subclasses should get a transient ID when needed from the imodel in the global @see [[MeasurementSelectionSet]]. If the imodel
* changes, the transient ID of all measurements is reset. Normally you do not need to do this yourself.
*/
get transientId() {
return this._transientId;
}
set transientId(newId) {
const prevId = this._transientId;
this._transientId = newId;
this.onTransientIdChanged(prevId);
}
get drawingMetadata() {
return this._drawingMetadata;
}
set drawingMetadata(data) {
this._drawingMetadata = data;
this.onDrawingMetadataChanged();
}
get worldScale() {
return this.drawingMetadata?.worldScale ?? 1.0;
}
set sheetViewId(id) {
this.viewTarget.addViewIds(id ?? []);
}
/** Gets or sets if the measurement should be drawn. */
get isVisible() {
return this._isVisible;
}
set isVisible(v) {
this._isVisible = v;
}
/** Gets or sets if the measurement is locked. Locked measurements have a different styling, cannot be edited, and cannot be cleared by the clear measurement tool. */
get isLocked() {
return this._isLocked;
}
set isLocked(value) {
// Do nothing if state didn't actually change
if (this._isLocked === value)
return;
this._isLocked = value;
this.onLockToggled();
}
/** Gets or sets the group ID for the measurement. This is used to categorize the measurement in a meaningful way to the application. */
get groupId() {
return this._groupId;
}
set groupId(value) {
const prevGroupId = this._groupId;
this._groupId = value;
this.onGroupIdChanged(prevGroupId);
}
/** Gets or sets the subgroup ID for the measurement. This is used to categorize the measurement in a meaningful way to the application. */
get subgroupId() {
return this._subgroupId;
}
set subgroupId(value) {
const prevSubgroupId = this._subgroupId;
this._subgroupId = value;
this.onSubGroupIdChanged(prevSubgroupId);
}
/** Gets or sets the ID for the measurement. This is used to categorize the measurement in a meaningful way to the application. */
get id() {
return this._id;
}
set id(value) {
const prevId = this._id;
this._id = value;
this.onIdChanged(prevId);
}
/** Gets or sets the display label for the measurement. */
get label() {
return this._label;
}
set label(value) {
const prevLabel = this._label;
this._label = value;
this.onLabelChanged(prevLabel);
}
/** Gets or sets the mame of the StyleSet to apply when drawing this measurement. If undefined, the default style is used. */
get style() {
return this._style;
}
set style(value) {
const prevStyle = this._style;
this._style = value;
this.onStyleChanged(false, prevStyle);
}
/** Gets or sets the name of the StyleSet to apply when drawing this measurement when it is locked. If undefined, the default lock style is used. */
get lockStyle() {
return this._lockStyle;
}
set lockStyle(value) {
const prevStyle = this._lockStyle;
this._lockStyle = value;
this.onStyleChanged(true, prevStyle);
}
/**
* Computes the active style for the measurement.
* @returns the active style based on the locking flag. If the style is undefined, the default style is returned (default when unlocked, default-locked when locked).
*/
get activeStyle() {
if (this.isLocked) {
return (this._lockStyle) ? this._lockStyle : WellKnownMeasurementStyle.DefaultLocked;
}
else {
return (this._style) ? this._style : WellKnownMeasurementStyle.Default;
}
}
/** Gets the views this measurement targets. */
get viewTarget() {
return this._viewTarget;
}
get displayLabels() { return this._displayLabels; }
set displayLabels(display) {
if (this._displayLabels !== display) {
this._displayLabels = display;
this.onDisplayLabelsToggled();
}
}
/** Communicates to the action toolbar this measurement wants to participate in actions. */
get allowActions() { return true; }
/** Protected constructor */
constructor(props) {
this._isLocked = false;
this._isVisible = true;
this._displayLabels = MeasurementPreferences.current.displayMeasurementLabels;
this._viewTarget = new MeasurementViewTarget();
if (props?.drawingMetadata)
this.drawingMetadata = DrawingMetadata.fromJSON(props.drawingMetadata);
}
/** Copies the measurement data into a new instance.
* @returns clone of this measurement.
*/
clone() {
const copy = this.createNewInstance();
copy.copyFrom(this);
return copy;
}
/**
* Serializes the measurement's data into a JSON prop object. Subclasses participate by overriding the writeToJSON method.
* @returns prop object containing the values of the measurement.
*/
toJSON() {
const json = {};
this.writeToJSON(json);
return json;
}
/**
* Tests equality with another measurement.
* @param other Measurement to test equality for.
* @param opts Options for equality testing.
* @returns true if the other measurement is equal, false if some property is not the same or if the measurement is not of the same type.
*/
equals(other, opts) {
// Reject if not same type
if (Object.getPrototypeOf(this).constructor !== Object.getPrototypeOf(other).constructor)
return false;
let ignoreStyle = false;
let ignoreViewTarget = false;
let ignoreIds = false;
let ignoreLabel = false;
let ignoreNonDataState = false;
if (opts) {
if (opts.ignoreStyle)
ignoreStyle = true;
if (opts.ignoreViewTarget)
ignoreViewTarget = true;
if (opts.ignoreIds)
ignoreIds = true;
if (opts.ignoreNonDataState)
ignoreNonDataState = true;
if (opts.ignoreLabel)
ignoreLabel = true;
}
if (!ignoreStyle) {
if (this._style !== other._style || this._lockStyle !== other._lockStyle)
return false;
}
if (!ignoreViewTarget) {
if (!this._viewTarget.equals(other._viewTarget))
return false;
}
if (!ignoreIds) {
if (this._id !== other._id || this._subgroupId !== other._subgroupId || this._groupId !== other._groupId)
return false;
}
if (!ignoreLabel) {
if (this._label !== other._label)
return false;
}
if (!ignoreNonDataState) {
if (this._isLocked !== other._isLocked)
return false;
if (this._displayLabels !== other._displayLabels)
return false;
}
return true;
}
/** Draw the measurement. This is called every frame, e.g. when the mouse moves. This is suitable for small or dynamic graphics, but if the measurement
* has complicated graphics, consider using the decorateCached method.
* @param context Decorate context for drawing to a viewport.
*/
decorate(_context) { }
/** Draw any graphics that need to be cached. This is called when the scene changes, e.g. zoom or rotate.
* @param context Decorate context for drawing to a viewport.
*/
decorateCached(_context) { }
/** Test if the measurement was picked.
* @param _pickContext Picking context to test against.
* @returns true if the measurement has been picked, false otherwise.
*/
testDecorationHit(_pickContext) { return false; }
/** Get a geometry stream representing the pickable geometry of the measurement. Usually this is simplier geometry than what is drawn.
* @param _pickContext Picking context to test against.
* @returns a geometry stream of pickable data or undefined.
*/
getDecorationGeometry(_pickContext) { return undefined; }
/** Get a tooltip for this measurement.
* @param _pickContext Picking context to test against.
* @returns a tooltip HTML element or string.
*/
async getDecorationToolTip(_pickContext) { return ""; }
/**
* Register a handler to modify the behavior of the widget data shown.
* @param handler Function that modifies the WidgetData
* @param priority Priority of the handler, highest priority will execute last
*/
static registerDataForMeasurementWidgetHandler(handler, priority = 0) {
if (undefined === Measurement._dataForMeasurementWidgetHandlers.find((value) => value.handlerFunction === handler)) {
Measurement._dataForMeasurementWidgetHandlers.push({ priority, handlerFunction: handler });
return true;
}
else
throw new Error("This MeasurementDataWidgetHandlerFunction is already registered.");
}
/**
* Gets formatted property data for the measurement UI widget.
* @returns promise with data or undefined if there is no data to display.
*/
async getDataForMeasurementWidget() {
const data = await this.getDataForMeasurementWidgetInternal();
if (data !== undefined) {
for (const handler of Measurement._dataForMeasurementWidgetHandlers.sort((h) => h.priority))
await handler.handlerFunction(this, data);
}
return data;
}
/**
* Gets formatted property data for the measurement UI widget.
* @returns promise with data or undefined if there is no data to display.
*/
async getDataForMeasurementWidgetInternal() { return undefined; }
/**
* Responds to a button event. By default this tests if the measurement was picked and opens the action toolbar.
* @param pickContext Picking context to test against.
* @returns Whether or not the event was handled.
*/
async onDecorationButtonEvent(pickContext) {
const ev = pickContext.buttonEvent;
if (!ev)
return MeasurementButtonHandledEvent.No;
// If left clicked, we want to select. If right we want to open the context toolbar. To select we should not consume the event.
if (this.testDecorationHit(pickContext)) {
if (ev.button === BeButton.Reset && !ev.isDown && !ev.isAltKey && !ev.isControlKey && !ev.isShiftKey && !ev.isAltKey && !ev.isDragging && !ev.isDoubleClick) {
ShimFunctions.defaultButtonEventAction(this, pickContext);
return MeasurementButtonHandledEvent.YesConsumeEvent;
}
return MeasurementButtonHandledEvent.Yes;
}
return MeasurementButtonHandledEvent.No;
}
/** Cleans up any resources (e.g. IDisposable objects) */
onCleanup() { }
/** Adjusts a point to account for Global Origin. This is required in order to display coordinates as text.
* May return a reference to the original point, or a new point if the global origin is applied
*/
adjustPointForGlobalOrigin(point) {
if (this.viewTarget.isOfViewType(WellKnownViewType.AnySpatial)) {
const iModel = UiFramework.getIModelConnection();
if (iModel) {
const globalOrigin = iModel.globalOrigin;
return point.minus(globalOrigin);
}
}
return point;
}
/** Adjusts point with the sheetToWorldTransform
* This is used to display 3d world information in sheets
*/
adjustPointWithSheetToWorldTransform(point) {
if (this.drawingMetadata?.sheetToWorldTransform)
return SheetMeasurementsHelper.measurementTransform(point, this.drawingMetadata.sheetToWorldTransform);
return point;
}
/**
* Creates a new instance of the measurement subclass. This works well for most subclasses that have a parameterless constructor (or one that takes in an optional props object).
* Otherwise, the subclass should override this if it has special needs to correctly instantiate a new instance of itself.
*/
createNewInstance() {
const ctor = Object.getPrototypeOf(this).constructor;
return new ctor();
}
/**
* Copies data from the other measurement into this instance.
* @param other Measurement to copy property values from.
*/
copyFrom(other) {
// We do want the onXYZ methods called so not using the private variables
this.isLocked = other.isLocked;
this.groupId = other.groupId;
this.subgroupId = other.subgroupId;
this.id = other.id;
this.label = other.label;
this.style = other.style;
this.lockStyle = other.lockStyle;
this.viewTarget.copyFrom(other.viewTarget);
this.displayLabels = other.displayLabels;
if (other.drawingMetadata)
this._drawingMetadata = { origin: other.drawingMetadata.origin.clone(), worldScale: other.drawingMetadata.worldScale, drawingId: other.drawingMetadata.drawingId, extents: other.drawingMetadata.extents?.clone(), sheetToWorldTransform: other.drawingMetadata.sheetToWorldTransform };
}
/**
* Deserializes properties (if they exist) from the JSON object.
* @param json JSON object to read data from.
*/
readFromJSON(json) {
this._isLocked = (json.isLocked !== undefined) ? json.isLocked : false;
this._groupId = (json.groupId !== undefined) ? json.groupId : undefined;
this._subgroupId = (json.subgroupId !== undefined) ? json.subgroupId : undefined;
this._id = (json.id !== undefined) ? json.id : undefined;
this._label = (json.label !== undefined) ? json.label : undefined;
this._style = (json.style !== undefined) ? json.style : undefined;
this._lockStyle = (json.style !== undefined) ? json.lockStyle : undefined;
this._displayLabels = (json.displayLabels !== undefined) ? json.displayLabels : MeasurementPreferences.current.displayMeasurementLabels;
if (json.drawingMetadata !== undefined)
this.drawingMetadata = DrawingMetadata.fromJSON(json.drawingMetadata);
if (json.viewTarget !== undefined) {
this._viewTarget.loadFromJSON(json.viewTarget);
}
else {
this._viewTarget.clear(); // Default to "Any" if nothing incoming
}
// Ensure we can read the old legacy way some measurements serialized view information (not all did this), if this exists then
// the json will not have a viewTarget so will default to "Any" (and skip if the value = 0 which also meant "Any").
const jsonAny = json;
if (jsonAny.viewportType !== undefined) {
const legacyVpType = json.viewportType;
if (typeof legacyVpType === "number") {
switch (legacyVpType) {
case 1: // MainOnly
this._viewTarget.include(WellKnownViewType.Spatial);
break;
case 2: // XSection
this._viewTarget.include(WellKnownViewType.XSection);
break;
case 3: // Profile
this._viewTarget.include(WellKnownViewType.Profile);
break;
}
}
}
}
/**
* Serializes properties to a JSON object.
* @param json JSON object to append data to.
*/
writeToJSON(json) {
json.isLocked = this._isLocked;
json.groupId = this._groupId;
json.subgroupId = this._subgroupId;
json.id = this._id;
json.label = this._label;
json.style = this._style;
json.lockStyle = this._lockStyle;
json.viewTarget = this._viewTarget.toJSON();
json.displayLabels = this._displayLabels;
const drawingMetadataJson = DrawingMetadata.toJSON(this.drawingMetadata);
if (drawingMetadataJson)
json.drawingMetadata = drawingMetadataJson;
}
/** Notify subclasses that style options have changed. This is to allow implementations to regenerate any cached graphics.
* @param _isLock true if the lock style was changed, false if the regular style.
* @param _prevStyle The previous style name.
*/
onStyleChanged(_isLock, _prevStyle) { }
/** Notify subclasses the group ID changed.
* @param _prevGroupid The previous group ID.
*/
onGroupIdChanged(_prevGroupid) { }
/** Notify subclasses the subgroup ID changed.
* @param _prevSubgroupId The previous subgroup ID.
*/
onSubGroupIdChanged(_prevSubgroupId) { }
/** Notify subclasses the ID changed.
* @param _prevId The previous ID.
*/
onIdChanged(_prevId) { }
/**
* Notify subclasses the label changed.
* @param _prevLabel The previous label value.
*/
onLabelChanged(_prevLabel) { }
/** Notify subclasses when the measurement's lock is toggled. */
onLockToggled() { }
/** Notify subclasses when the display units have changed. */
onDisplayUnitsChanged() { }
/** Notify subclasses when the transient ID has changed.
* @param _prevId The previous ID, if any.
*/
onTransientIdChanged(_prevId) { }
/**
* Notify subclasses when DrawingMetadata changes
*/
onDrawingMetadataChanged() { }
/**
* Notify subclasses when the display labels property has changed.
*/
onDisplayLabelsToggled() { }
/** Parses a JSON object into a single or multiple measurements.
* @param data JSON object
* @returns single or multiple measurements or undefine if parsing failed, or the JSON object is undefined.
*/
static parse(data) {
if (!data)
return undefined;
const parsed = new Array();
// Can have an array where each entry is "measureTypeName: data" or a single object that has multiple "measureTypeName" properties.
// What's important though is each of the "data" can be a single measurement object or an array of measurement objects. We support lots of ways
// to organize the data, although the serialize method will output an array of single-property objects if there are multiple types.
const arr = Array.isArray(data) ? data : [data];
// For each entry in array...
for (const entry of arr) {
// Look at the entry with each registered parser. Each looks for a named property on the object and parses that. Multiple named properties can exist
// on a single object, so we let each serializer have a shot.
for (const kv of Measurement._serializers) {
const serializer = kv[1];
const measurement = serializer.parse(entry);
if (!measurement)
continue;
if (Array.isArray(measurement))
parsed.push(...measurement);
else
parsed.push(measurement);
}
}
// Avoid returning arrays of one object...
if (parsed.length > 0)
return (parsed.length === 1) ? parsed[0] : parsed;
return undefined;
}
/** Parses a JSON object into a single measurement. If the JSON object had multiple measurements, only the first
* one is returned.
* @param data JSON object
* @returns single measurement or undefined if parsing failed.
*/
static parseSingle(data) {
const measurement = Measurement.parse(data);
if (measurement === undefined)
return undefined;
if (Array.isArray(measurement) && measurement.length > 0)
return measurement[0];
return measurement;
}
/** Serializes one or more measurements into one or more JSON objects. For each type of measurement, the result will be a JSON object with a single
* named property representing the measuremnt (e.g. "distanceMeasurement") which itself will either be a single JSON object or an array if more than one. If all measurements
* are the same type, a single JSON object is returned. If there are multiple measurement types (e.g. distance, area, etc) then an array is returned where each entry is a JSON
* object with the single named property.
* @param measurement one or more measurements.
* @returns undefined if nothing could be serialized OR a single JSON object if all the measurements are of the same type OR an array of JSON objects.
*/
static serialize(measurement) {
if (Array.isArray(measurement)) {
const buckets = new Map();
// First pass, bucket all incoming measurements
for (const m of measurement) {
if (m === undefined || m.serializer === undefined)
continue;
let bucket = buckets.get(m.serializer.measurementName);
if (!bucket) {
bucket = new Array();
buckets.set(m.serializer.measurementName, bucket);
}
bucket.push(m);
}
if (buckets.size === 0)
return undefined;
// Second pass, proces each bucket
const dataArray = [];
for (const bucket of buckets) {
const serializer = Measurement.findSerializer(bucket[0]);
if (serializer) {
const data = serializer.serialize(bucket[1]);
if (data)
dataArray.push(data);
}
}
// If only one bucket of measurements, avoid returning an array and just return the single entry
if (dataArray.length === 1)
return dataArray[0];
return (dataArray.length > 0) ? dataArray : undefined;
}
else {
const serializer = measurement.serializer;
if (serializer) {
const data = serializer.serialize(measurement);
if (data)
return data;
}
}
return undefined;
}
/** Registers a measurement serializer that is used in the parse/serialize static methods. Once a serializer is registered, it cannot be dropped.
* If you need to override a measurement, do so by subclassing the measurement and it's serializer.
* @param serializer serializer that handles a unique measurement during parsing/serialization.
* @throws error if the serializer's name is not unique.
*/
static registerSerializer(serializer) {
if (Measurement._serializers.has(serializer.measurementName))
throw new Error(`Measurement serializer names MUST be unique. Duplicate: ${serializer.measurementName}`);
this._serializers.set(serializer.measurementName, serializer);
return serializer;
}
/**
* Finds a serializer for the given name.
* @param measurementName measurement name that a serializer is associated with.
* @returns the associated serializer or undefined if none exists for the given name.
*/
static findSerializer(measurementName) {
return Measurement._serializers.get(measurementName);
}
/**
* Invalidate decorations for all viewports that can draw the specified measurements.
* @param measurements Array of measurements to invalidate views for.
*/
static invalidateDecorationsForAll(measurements) {
if (measurements.length === 0)
return;
const target = new MeasurementViewTarget();
// Only need to look at the included view types for each measurement. If any one measurement is "any", it doesn't matter if it excludes
// nor do we need to keep collecting view types, since we will most likely need to draw all views. Any whacky cases like include "any" but
// explicitly exclude all types of views are not considered. We could classify all views and check that, but may as wel Keep It Simple.
for (const m of measurements) {
if (m.viewTarget.primary === WellKnownViewType.Any) {
target.clear();
break;
}
for (const viewType of m.viewTarget.included)
target.include(viewType);
}
target.invalidateViewportDecorations();
}
}
Measurement._serializers = new Map();
Measurement._dataForMeasurementWidgetHandlers = [];
/** Default drawing style name. */
Measurement.defaultStyle = WellKnownMeasurementStyle.Default;
/** Default locked drawing styl name. */
Measurement.defaultLockStyle = WellKnownMeasurementStyle.DefaultLocked;
//# sourceMappingURL=Measurement.js.map