Skip to content

Commit 304d56b

Browse files
committed
feat: add crs support
1 parent b6ed3c1 commit 304d56b

File tree

10 files changed

+180
-6
lines changed

10 files changed

+180
-6
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,5 @@ resources/frags/test
135135
resources/asdf2.frag
136136
resources/asdf2.json
137137

138-
.DS_Store
138+
.DS_Store
139+
.claude/

packages/fragments/src/FragmentsModels/src/model/data-manager.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
CRSData,
23
MultiThreadingRequestClass,
34
ItemsQueryParams,
45
SpatialTreeItem,
@@ -114,6 +115,14 @@ export class DataManager {
114115
return model.threads.invoke(model.modelId, "getMetadata", []) as Promise<T>;
115116
}
116117

118+
async getCRS(model: FragmentsModel) {
119+
return model.threads.invoke(
120+
model.modelId,
121+
"getCRS",
122+
[],
123+
) as Promise<CRSData | null>;
124+
}
125+
117126
async getGuidsByLocalIds(model: FragmentsModel, localIds: number[]) {
118127
return model.threads.invoke(model.modelId, "getGuidsByLocalIds", [
119128
localIds,

packages/fragments/src/FragmentsModels/src/model/fragments-model.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,15 @@ export class FragmentsModel {
259259
return this._dataManager.getMetadata<T>(this);
260260
}
261261

262+
/**
263+
* Get the Coordinate Reference System (CRS) data of the model, if available.
264+
* Returns null if the source IFC file did not contain IFCPROJECTEDCRS
265+
* or IFCCOORDINATEREFERENCESYSTEM entities.
266+
*/
267+
async getCRS() {
268+
return this._dataManager.getCRS(this);
269+
}
270+
262271
/**
263272
* Get the GUIDs corresponding to the specified local IDs.
264273
* @param localIds - Array of local IDs to look up.

packages/fragments/src/FragmentsModels/src/model/model-types.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,42 @@ export type GridData = {
420420
wAxes: GridAxisData[];
421421
};
422422

423+
/**
424+
* Interface representing the Coordinate Reference System (CRS) data
425+
* extracted from an IFC model's IFCPROJECTEDCRS and IFCMAPCONVERSION entities.
426+
*/
427+
export interface CRSData {
428+
/** The CRS name/identifier, e.g. "EPSG:3947" */
429+
name: string | null;
430+
/** Description of the CRS, e.g. "RGF93_CC47" */
431+
description: string | null;
432+
/** The geodetic datum name, e.g. "RGF93" */
433+
geodeticDatum: string | null;
434+
/** The vertical datum name */
435+
verticalDatum: string | null;
436+
/** The map projection name */
437+
mapProjection: string | null;
438+
/** The map zone identifier */
439+
mapZone: string | null;
440+
/** The map unit name, e.g. "METRE" */
441+
mapUnit: string | null;
442+
/** Map conversion parameters from IFCMAPCONVERSION (null if not present) */
443+
mapConversion: {
444+
/** Easting coordinate of the map origin */
445+
eastings: number;
446+
/** Northing coordinate of the map origin */
447+
northings: number;
448+
/** Orthogonal height of the map origin */
449+
orthogonalHeight: number;
450+
/** X component of the abscissa of the X axis */
451+
xAxisAbscissa: number;
452+
/** Y component of the ordinate of the X axis */
453+
xAxisOrdinate: number;
454+
/** Scale factor applied to the map */
455+
scale: number;
456+
} | null;
457+
}
458+
423459
export type CustomDataItem = {
424460
data: {
425461
value: string;

packages/fragments/src/FragmentsModels/src/single-threading/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ export class SingleThreadedFragmentsModel {
8888
return this._virtualModel.getMetadata() as T;
8989
}
9090

91+
/**
92+
* Get the Coordinate Reference System (CRS) data of the model, if available.
93+
*/
94+
getCRS() {
95+
return this._virtualModel.getCRS();
96+
}
97+
9198
/**
9299
* Get the GUIDs corresponding to the specified local IDs.
93100
* @param localIds - Array of local IDs to look up.

packages/fragments/src/FragmentsModels/src/virtual-model/virtual-controllers/virtual-properties-controller.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Meshes, Model, SpatialStructure } from "../../../../Schema";
55
import { Identifier } from "../../model";
66
import {
77
AttributesUniqueValuesParams,
8+
CRSData,
89
GetItemsByAttributeParams,
910
GetItemsByRelationParams,
1011
ItemData,
@@ -154,6 +155,14 @@ export class VirtualPropertiesController {
154155
return JSON.parse(metadata);
155156
}
156157

158+
getCRS(): CRSData | null {
159+
const metadata = this.getMetadata();
160+
if (!metadata || !metadata.crs) {
161+
return null;
162+
}
163+
return metadata.crs as CRSData;
164+
}
165+
157166
getItemIdsFromLocalIds(localIds?: Iterable<number>): number[] {
158167
if (!localIds) {
159168
return Array.from(this._model.meshes()!.meshesItemsArray()!);

packages/fragments/src/FragmentsModels/src/virtual-model/virtual-fragments-model.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ export class VirtualFragmentsModel {
189189
return this.properties.getMetadata();
190190
}
191191

192+
getCRS() {
193+
// If there are any changes to the metadata, check there too
194+
const found = EditUtils.applyChangesToSpecialData(
195+
this.requests,
196+
"METADATA",
197+
);
198+
if (found && found.crs) {
199+
return found.crs;
200+
}
201+
202+
return this.properties.getCRS();
203+
}
204+
192205
getLocalIdsByGuids(guids: string[]) {
193206
return this.properties.getLocalIdsByGuids(guids);
194207
}

packages/fragments/src/Importers/IfcImporter/src/properties/property-processor.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,12 +673,99 @@ export class IfcPropertyProcessor {
673673
const descriptions: string[] = [];
674674
this.getMetadataRecursively(rawDescription.arguments, descriptions);
675675

676-
const metadata = { schema, names, descriptions } as any;
676+
const crs = this.extractCRS(ifcApi);
677+
678+
const metadata = { schema, names, descriptions, crs } as any;
677679

678680
const metadataOffset = this._builder.createString(JSON.stringify(metadata));
679681
return metadataOffset;
680682
}
681683

684+
private extractCRS(ifcApi: WEBIFC.IfcAPI) {
685+
// Try IFCPROJECTEDCRS first, then fall back to IFCCOORDINATEREFERENCESYSTEM
686+
let crsEntity: any = null;
687+
try {
688+
const ids = ifcApi.GetLineIDsWithType(0, WEBIFC.IFCPROJECTEDCRS);
689+
if (ids.size() > 0) {
690+
crsEntity = ifcApi.GetLine(0, ids.get(0));
691+
}
692+
} catch {
693+
// IFCPROJECTEDCRS may not exist in older IFC schemas
694+
}
695+
696+
if (!crsEntity) {
697+
try {
698+
const ids = ifcApi.GetLineIDsWithType(
699+
0,
700+
WEBIFC.IFCCOORDINATEREFERENCESYSTEM,
701+
);
702+
if (ids.size() > 0) {
703+
crsEntity = ifcApi.GetLine(0, ids.get(0));
704+
}
705+
} catch {
706+
// Not available
707+
}
708+
}
709+
710+
if (!crsEntity) {
711+
return null;
712+
}
713+
714+
const name = this.unwrapValue(crsEntity.Name) ?? null;
715+
const description = this.unwrapValue(crsEntity.Description) ?? null;
716+
const geodeticDatum = this.unwrapValue(crsEntity.GeodeticDatum) ?? null;
717+
const verticalDatum = this.unwrapValue(crsEntity.VerticalDatum) ?? null;
718+
const mapProjection = this.unwrapValue(crsEntity.MapProjection) ?? null;
719+
const mapZone = this.unwrapValue(crsEntity.MapZone) ?? null;
720+
721+
// Resolve MapUnit reference to get the unit name
722+
let mapUnit: string | null = null;
723+
try {
724+
const unitRef = crsEntity.MapUnit;
725+
if (unitRef && typeof unitRef === "object" && unitRef.type === 5) {
726+
const unitEntity = ifcApi.GetLine(0, unitRef.value);
727+
if (unitEntity) {
728+
const unitName = this.unwrapValue(unitEntity.Name);
729+
if (unitName) {
730+
mapUnit = String(unitName);
731+
}
732+
}
733+
}
734+
} catch {
735+
// Unit resolution failed
736+
}
737+
738+
// Extract IFCMAPCONVERSION if present
739+
let mapConversion = null;
740+
try {
741+
const convIds = ifcApi.GetLineIDsWithType(0, WEBIFC.IFCMAPCONVERSION);
742+
if (convIds.size() > 0) {
743+
const conv = ifcApi.GetLine(0, convIds.get(0));
744+
mapConversion = {
745+
eastings: this.unwrapValue(conv.Eastings) ?? 0,
746+
northings: this.unwrapValue(conv.Northings) ?? 0,
747+
orthogonalHeight: this.unwrapValue(conv.OrthogonalHeight) ?? 0,
748+
xAxisAbscissa: this.unwrapValue(conv.XAxisAbscissa) ?? 1,
749+
xAxisOrdinate: this.unwrapValue(conv.XAxisOrdinate) ?? 0,
750+
scale: this.unwrapValue(conv.Scale) ?? 1,
751+
};
752+
}
753+
} catch {
754+
// IFCMAPCONVERSION may not exist
755+
}
756+
757+
return {
758+
name,
759+
description,
760+
geodeticDatum,
761+
verticalDatum,
762+
mapProjection,
763+
mapZone,
764+
mapUnit,
765+
mapConversion,
766+
};
767+
}
768+
682769
private getMetadataRecursively(source: any[], target: string[]) {
683770
for (const item of source) {
684771
if (item === null || item === undefined) continue;

packages/fragments/src/Importers/IfcImporter/test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const run = async (serialize: boolean) => {
3333

3434
// Read the file
3535
// const ifcFile = fs.readFileSync(`${name}${extension}`)
36-
const url = "/resources/ifc/test/small_test.ifc";
36+
const url = "/resources/ifc/test/crs.ifc";
3737
const ifcFile = await fetch(url);
3838
const ifcBuffer = await ifcFile.arrayBuffer();
3939
const typedArray = new Uint8Array(ifcBuffer);
@@ -145,6 +145,9 @@ const run = async (serialize: boolean) => {
145145
raw,
146146
});
147147

148+
const crs = await model.getCRS();
149+
console.log(crs);
150+
148151
world.scene.three.add(model.object);
149152

150153
const absoluteAlignments = await model.getAlignments();

resources/worker.mjs

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)