/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { extractGeoreferencingOnDemand, extractLengthUnitScale, type GeoreferenceInfo, type IfcDataStore, type MapConversion, type ProjectedCRS, } from '@ifc-lite/parser'; import type { CoordinateInfo } from '@ifc-lite/geometry'; import { detectScaleUnitMismatch, getEffectiveHorizontalScale, inferMapUnitScale, type ScaleUnitMismatch, } from './geo-scale'; export { detectScaleUnitMismatch, getEffectiveHorizontalScale, inferMapUnitScale, type ScaleUnitMismatch, } from './geo-scale'; export interface GeorefMutationDataLike { projectedCRS?: Partial; mapConversion?: Partial; } export interface EffectiveGeoreference extends GeoreferenceInfo { hasGeoreference: true; coordinateInfo?: CoordinateInfo; lengthUnitScale: number; } export function hasStandardGeoreferencing( georef: Pick | null | undefined, ): boolean { return Boolean( georef && georef.source !== 'siteLocation' && georef.projectedCRS?.name && georef.mapConversion, ); } export function supportsStandardGeoreferencing( schemaVersion: string | undefined, georef: Pick | null | undefined, ): boolean { if (hasStandardGeoreferencing(georef)) return true; // Any extracted IfcProjectedCRS / IfcMapConversion makes editing useful, // regardless of the declared schema. IFC2X3 files commonly carry these // via extensions; once we've parsed them, surface the full editor instead // of hiding behind a schema-version notice that contradicts what the // properties panel already shows for the same entities. if ( georef && georef.source !== 'siteLocation' && (georef.projectedCRS?.name || georef.mapConversion) ) { return true; } return !schemaVersion?.toUpperCase().includes('2X3'); } export function getIfcLengthUnitScale(dataStore: IfcDataStore | null | undefined): number { if (!dataStore?.source?.length || !dataStore.entityIndex) return 1; return extractLengthUnitScale(dataStore.source, dataStore.entityIndex); } export function mergeProjectedCRS( original: ProjectedCRS | undefined, mutations: Partial | undefined, lengthUnitScale: number, ): ProjectedCRS | undefined { if (!original && !mutations) return undefined; const mapUnit = mutations?.mapUnit ?? original?.mapUnit; const mapUnitScale = mutations?.mapUnit !== undefined ? inferMapUnitScale(mapUnit, lengthUnitScale) : original?.mapUnitScale ?? inferMapUnitScale(mapUnit, undefined); return { id: original?.id ?? 0, name: (mutations?.name ?? original?.name ?? '') as string, description: mutations?.description ?? original?.description, geodeticDatum: mutations?.geodeticDatum ?? original?.geodeticDatum, verticalDatum: mutations?.verticalDatum ?? original?.verticalDatum, mapProjection: mutations?.mapProjection ?? original?.mapProjection, mapZone: mutations?.mapZone ?? original?.mapZone, mapUnit, mapUnitScale, }; } export function mergeMapConversion( original: MapConversion | undefined, mutations: Partial | undefined, ): MapConversion | undefined { if (!original && !mutations) return undefined; return { id: original?.id ?? 0, sourceCRS: original?.sourceCRS ?? 0, targetCRS: original?.targetCRS ?? 0, eastings: (mutations?.eastings ?? original?.eastings ?? 0) as number, northings: (mutations?.northings ?? original?.northings ?? 0) as number, orthogonalHeight: (mutations?.orthogonalHeight ?? original?.orthogonalHeight ?? 0) as number, xAxisAbscissa: mutations?.xAxisAbscissa ?? original?.xAxisAbscissa, xAxisOrdinate: mutations?.xAxisOrdinate ?? original?.xAxisOrdinate, scale: mutations?.scale ?? original?.scale, }; } export function getEffectiveGeoreference( dataStore: IfcDataStore | null | undefined, coordinateInfo?: CoordinateInfo, mutations?: GeorefMutationDataLike, ): EffectiveGeoreference | null { if (!dataStore) return null; const original = extractGeoreferencingOnDemand(dataStore); const lengthUnitScale = getIfcLengthUnitScale(dataStore); const projectedCRS = mergeProjectedCRS( original?.projectedCRS, mutations?.projectedCRS, lengthUnitScale, ); const mapConversion = mergeMapConversion(original?.mapConversion, mutations?.mapConversion); if (!projectedCRS && !mapConversion) return null; return { hasGeoreference: true, projectedCRS, mapConversion, coordinateInfo, lengthUnitScale, source: original?.source, transformMatrix: original?.transformMatrix, }; }