UNPKG

@itwin/measure-tools-react

Version:
462 lines 20.5 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { Geometry, IModelJson, Point3d, PointString3d, } from "@itwin/core-geometry"; import { Cartographic } from "@itwin/core-common"; import { GraphicType, IModelApp } from "@itwin/core-frontend"; import { FormatterUtils } from "../api/FormatterUtils.js"; import { StyleSet, WellKnownGraphicStyleType, WellKnownTextStyleType, } from "../api/GraphicStyle.js"; import { Measurement, MeasurementPickContext, MeasurementSerializer, } from "../api/Measurement.js"; import { WellKnownViewType } from "../api/MeasurementEnums.js"; import { MeasurementPreferences } from "../api/MeasurementPreferences.js"; import { MeasurementPropertyHelper } from "../api/MeasurementPropertyHelper.js"; import { MeasurementSelectionSet } from "../api/MeasurementSelectionSet.js"; import { TextMarker } from "../api/TextMarker.js"; import { MeasureTools } from "../MeasureTools.js"; /** Serializer for a [[LocationMeasurement]]. */ export class LocationMeasurementSerializer extends MeasurementSerializer { get measurementName() { return LocationMeasurementSerializer.locationMeasurementName; } isValidType(measurement) { return measurement instanceof LocationMeasurement; } isValidJSON(json) { if (!super.isValidJSON(json) || !json.hasOwnProperty("location")) return false; return true; } parseSingle(data) { if (!this.isValidJSON(data)) return undefined; const props = data; return LocationMeasurement.fromJSON(props); } } LocationMeasurementSerializer.locationMeasurementName = "locationMeasurement"; /** * Location measurement. A point somewhere in the world, optionally with other values (such as slope, station, offset, etc). */ export class LocationMeasurement extends Measurement { get location() { return this._location; } set location(pt) { this._location.setFrom(pt); this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } get geoLocation() { return this._geoLocation; } set geoLocation(geoLoc) { this._geoLocation = geoLoc; } get slope() { return this._slope; } set slope(slope) { this._slope = slope; } get station() { return this._station; } set station(station) { this._station = station; } get offset() { return this._offset; } set offset(offset) { this._offset = offset; } get isDynamic() { return this._isDynamic; } set isDynamic(v) { this._isDynamic = v; if (this._textMarker) this._textMarker.pickable = !v; } get lengthKoQ() { return this._lengthKoQ; } set lengthKoQ(value) { this._lengthKoQ = value; this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } get lengthPersistenceUnitName() { return this._lengthPersistenceUnitName; } set lengthPersistenceUnitName(value) { this._lengthPersistenceUnitName = value; this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } get stationKoQ() { return this._stationKoQ; } set stationKoQ(value) { this._stationKoQ = value; } get stationPersistenceUnitName() { return this._stationPersistenceUnitName; } set stationPersistenceUnitName(value) { this._stationPersistenceUnitName = value; } get angleKoQ() { return this._angleKoQ; } set angleKoQ(value) { this._angleKoQ = value; } get anglePersistenceUnitName() { return this._anglePersistenceUnitName; } set anglePersistenceUnitName(value) { this._anglePersistenceUnitName = value; } constructor(props) { super(props); this._location = Point3d.createZero(); this._isDynamic = false; this._lengthKoQ = "AecUnits.LENGTH"; this._lengthPersistenceUnitName = "Units.M"; this._stationKoQ = "RoadRailUnits.STATION"; this._stationPersistenceUnitName = "Units.M"; this._angleKoQ = "AecUnits.ANGLE"; this._anglePersistenceUnitName = "Units.RAD"; this.getSnapId(); // Preload transient ID"s since we normally don not have these as dynamic if (props) { this.readFromJSON(props); } this.populateFormattingSpecsRegistry().then(() => this.createTextMarker().catch()) .catch(); } /** Changes the location. Only possible if the measurement is dynamic. */ changeLocation(props) { if (!this.isDynamic) return false; this.readFromJSON(props); return true; } testDecorationHit(pickContext) { if (this.transientId && this.transientId === pickContext.geomId) return true; if (pickContext.buttonEvent && this._textMarker && this.displayLabels) return this._textMarker.pick(pickContext.buttonEvent.viewPoint); return false; } getDecorationGeometry(_pickContext) { return [ IModelJson.Writer.toIModelJson(PointString3d.create(this.location)), ]; } async getDecorationToolTip(_pickContext) { return MeasureTools.localization.getLocalizedString("MeasureTools:Measurements.locationMeasurement"); } getSnapId() { if (!this.transientId) this.transientId = MeasurementSelectionSet.nextTransientId; if (this.isDynamic) return undefined; return this.transientId; } onTransientIdChanged(_prevId) { if (this._textMarker) this._textMarker.transientHiliteId = this.transientId; } async populateFormattingSpecsRegistry(_force) { const lengthEntry = IModelApp.quantityFormatter.getSpecsByName(this._lengthKoQ); if (_force || !lengthEntry || lengthEntry.formatterSpec.persistenceUnit?.name !== this._lengthPersistenceUnitName) { const lengthFormatProps = await IModelApp.formatsProvider.getFormat(this._lengthKoQ); if (lengthFormatProps) { await IModelApp.quantityFormatter.addFormattingSpecsToRegistry(this._lengthKoQ, this._lengthPersistenceUnitName, lengthFormatProps); } } const stationEntry = IModelApp.quantityFormatter.getSpecsByName(this._stationKoQ); if (_force || !stationEntry || stationEntry.formatterSpec.persistenceUnit?.name !== this._stationPersistenceUnitName) { const stationFormatProps = await IModelApp.formatsProvider.getFormat(this._stationKoQ); if (stationFormatProps) { await IModelApp.quantityFormatter.addFormattingSpecsToRegistry(this._stationKoQ, this._stationPersistenceUnitName, stationFormatProps); } } const angleEntry = IModelApp.quantityFormatter.getSpecsByName(this._angleKoQ); if (_force || !angleEntry || angleEntry.formatterSpec.persistenceUnit?.name !== this._anglePersistenceUnitName) { const angleFormatProps = await IModelApp.formatsProvider.getFormat(this._angleKoQ); if (angleFormatProps) { await IModelApp.quantityFormatter.addFormattingSpecsToRegistry(this._angleKoQ, this._anglePersistenceUnitName, angleFormatProps); } } } decorate(context) { super.decorate(context); const styleTheme = StyleSet.getOrDefault(this.activeStyle); const style = styleTheme.getGraphicStyle(WellKnownGraphicStyleType.LocationMeasurement); if (this._textMarker && this.displayLabels) this._textMarker.addDecoration(context); if (this.isDynamic) return; const xBuilder = context.createGraphicBuilder(GraphicType.WorldDecoration, undefined, this.getSnapId()); style.addStyledPointString(xBuilder, [this._location], false); context.addDecorationFromBuilder(xBuilder); } async createTextMarker() { const adjustedLocation = this.adjustPointWithSheetToWorldTransform(this.adjustPointForGlobalOrigin(this._location)); const lengthSpec = IModelApp.quantityFormatter.getSpecsByName(this._lengthKoQ)?.formatterSpec; const entries = [ { label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.coordinate_x"), value: await FormatterUtils.formatLength(adjustedLocation.x, lengthSpec), }, { label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.coordinate_y"), value: await FormatterUtils.formatLength(adjustedLocation.y, lengthSpec), }, { label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.coordinate_z"), value: await FormatterUtils.formatLength(adjustedLocation.z, lengthSpec), }, ]; if (this._isDynamic) { entries.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.slope"), value: this._slope === undefined ? "" : FormatterUtils.formatSlope(100 * this._slope, false), }); } if (!this._textMarker) { const styleTheme = StyleSet.getOrDefault(this.activeStyle); this._textMarker = TextMarker.createHoverBox(entries, this._location, styleTheme); this._textMarker.pickable = !this.isDynamic; this._textMarker.transientHiliteId = this.transientId; this._textMarker.setMouseButtonHandler(this.handleTextMarkerButtonEvent.bind(this)); } else { this._textMarker.pickable = !this.isDynamic; this._textMarker.worldLocation = this._location; this._textMarker.textLines = entries; } } async getDataForMeasurementWidgetInternal() { const lengthSpec = IModelApp.quantityFormatter.getSpecsByName(this._lengthKoQ)?.formatterSpec; const angleSpec = IModelApp.quantityFormatter.getSpecsByName(this._angleKoQ)?.formatterSpec; const stationSpec = IModelApp.quantityFormatter.getSpecsByName(this._stationKoQ)?.formatterSpec; const adjustedLocation = this.adjustPointWithSheetToWorldTransform(this.adjustPointForGlobalOrigin(this._location)); const fCoordinates = FormatterUtils.formatCoordinatesImmediate(adjustedLocation, lengthSpec); let title = MeasureTools.localization.getLocalizedString("MeasureTools:Measurements.locationMeasurement"); title += ` [${fCoordinates}]`; const data = { title, properties: [] }; MeasurementPropertyHelper.tryAddNameProperty(this, data.properties); data.properties.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.coordinates"), name: "LocationMeasurement_Location", value: fCoordinates, }); if (this._geoLocation && this.drawingMetadata?.sheetToWorldTransform === undefined) data.properties.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.latLong"), name: "LocationMeasurement_LatLong", value: await FormatterUtils.formatCartographicToLatLong(this._geoLocation, angleSpec), }); if (MeasurementPreferences.current.displayLocationAltitude) { data.properties.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.altitude"), name: "LocationMeasurement_Altitude", value: await FormatterUtils.formatLength(adjustedLocation.z, lengthSpec), }); } if (this.drawingMetadata?.sheetToWorldTransform === undefined) { let slopeValue; if (undefined !== this._slope) slopeValue = FormatterUtils.formatSlope(100.0 * this._slope, true); else slopeValue = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.slopeUnavailable"); data.properties.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.slope"), name: "LocationMeasurement_Slope", value: slopeValue, }); } if (undefined !== this._station) { data.properties.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.station"), name: "LocationMeasurement_Station", value: await FormatterUtils.formatStation(this._station, stationSpec), }); } if (undefined !== this._offset) { data.properties.push({ label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.offset"), name: "LocationMeasurement_Offset", value: await FormatterUtils.formatLength(this._offset, lengthSpec), }); } return data; } handleTextMarkerButtonEvent(ev) { if (this._isDynamic) return false; // eslint-disable-next-line @typescript-eslint/no-floating-promises this.onDecorationButtonEvent(MeasurementPickContext.createFromSourceId("Invalid", ev)).catch(); return true; } onStyleChanged(_isLock, _prevStyle) { this.updateMarkerStyle(); } onLockToggled() { this.updateMarkerStyle(); } onDisplayUnitsChanged() { this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } updateMarkerStyle() { if (!this._textMarker) return; const styleTheme = StyleSet.getOrDefault(this.activeStyle); const tStyle = styleTheme.getTextStyle(WellKnownTextStyleType.HoverBox); this._textMarker.applyStyle(tStyle); } /** * 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) { if (!super.equals(other, opts)) return false; // Compare data (ignore isDynamic) const tol = opts && opts.tolerance !== undefined ? opts.tolerance : Geometry.smallMetricDistance; const otherLoc = other; if (otherLoc === undefined || !this._location.isAlmostEqual(otherLoc._location, tol) || !isNearlyEqual(this._offset, otherLoc._offset, tol) || !isNearlyEqual(this._slope, otherLoc._slope, tol) || !isNearlyEqual(this._station, otherLoc._station, tol)) return false; if (this._geoLocation !== undefined && otherLoc._geoLocation !== undefined) { if (!this._geoLocation.equalsEpsilon(otherLoc._geoLocation, tol)) return false; } else if (this._geoLocation !== otherLoc._geoLocation) { return false; } return true; } /** * Copies data from the other measurement into this instance. * @param other Measurement to copy property values from. */ copyFrom(other) { super.copyFrom(other); if (other instanceof LocationMeasurement) { this._isDynamic = other._isDynamic; this._location.setFrom(other._location); this._geoLocation = other._geoLocation ? other._geoLocation.clone() : undefined; this._slope = other._slope; this._offset = other._offset; this._station = other._station; this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } } /** * Deserializes properties (if they exist) from the JSON object. * @param json JSON object to read data from. */ readFromJSON(json) { super.readFromJSON(json); const jsonLoc = json; if (jsonLoc.location !== undefined) this._location.setFromJSON(jsonLoc.location); if (jsonLoc.geoLocation) this._geoLocation = Cartographic.fromRadians(jsonLoc.geoLocation); else this._geoLocation = undefined; if (jsonLoc.formatting?.length?.koqName) this._lengthKoQ = jsonLoc.formatting.length.koqName; if (jsonLoc.formatting?.length?.persistenceUnitName) this._lengthPersistenceUnitName = jsonLoc.formatting.length.persistenceUnitName; if (jsonLoc.formatting?.station?.koqName) this._stationKoQ = jsonLoc.formatting.station.koqName; if (jsonLoc.formatting?.station?.persistenceUnitName) this._stationPersistenceUnitName = jsonLoc.formatting.station.persistenceUnitName; if (jsonLoc.formatting?.angle?.koqName) this._angleKoQ = jsonLoc.formatting.angle.koqName; if (jsonLoc.formatting?.angle?.persistenceUnitName) this._anglePersistenceUnitName = jsonLoc.formatting.angle.persistenceUnitName; this._slope = jsonLoc.slope; this._station = jsonLoc.station; this._offset = jsonLoc.offset; this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } /** * Serializes properties to a JSON object. * @param json JSON object to append data to. */ writeToJSON(json) { super.writeToJSON(json); const jsonLoc = json; jsonLoc.location = this._location.toJSON(); if (this._geoLocation) { const geoLoc = this._geoLocation; jsonLoc.geoLocation = { latitude: geoLoc.latitude, longitude: geoLoc.longitude, height: geoLoc.height, }; } else { jsonLoc.geoLocation = undefined; } jsonLoc.slope = this._slope; jsonLoc.station = this._station; jsonLoc.offset = this._offset; jsonLoc.formatting = { length: { koqName: this._lengthKoQ, persistenceUnitName: this._lengthPersistenceUnitName, }, station: { koqName: this._stationKoQ, persistenceUnitName: this._stationPersistenceUnitName, }, angle: { koqName: this._angleKoQ, persistenceUnitName: this._anglePersistenceUnitName, }, }; } static create(location, viewType, formatting) { // Don't need to serialize the points, will just work as is const measurement = new LocationMeasurement({ location, formatting }); if (viewType) measurement.viewTarget.include(viewType); return measurement; } static fromJSON(props) { const locMeasurement = new LocationMeasurement(props); // LEGACY - Originally location measurements were hardcoded for "MainOnly", so if no viewTarget/viewportType default to "Spatial". So reading this from old JSON // we don"t want location measurement"s set to Any...but we want new measurements created to be set to Any if no view types are given! if (props.viewTarget === undefined && props.viewportType === undefined) locMeasurement.viewTarget.include(WellKnownViewType.Spatial); return locMeasurement; } } LocationMeasurement.serializer = Measurement.registerSerializer(new LocationMeasurementSerializer()); function isNearlyEqual(a, b, tol) { if (a !== undefined && b !== undefined) { if (!Geometry.isSameCoordinate(a, b, tol)) return false; } else if (a !== b) { return false; } return true; } //# sourceMappingURL=LocationMeasurement.js.map