Turns a point of an IFC model into map grid coordinates when the file has a map conversion, or into an approximate longitude and latitude from the site's reference point.
How the precision is kept
With a map conversion the point goes through one 64-bit 4 × 4 matrix and one multiplication by the meters per map unit. Without one, east and north offsets from the site's reference point are divided by the ellipsoid's radii of curvature at the site latitude, which is a first-order approximation that the code describes as fine across a site, not across a city. The WGS 84 constants are the defining values.
Calculations under this policy
5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
Degrees from degrees, minutes, seconds and millionths
64-bit
Reads an IFC compound angle as decimal degrees.
deg = d + m ÷ 60 + (s + μ ÷ 10⁶) ÷ 3600
The seconds and the millionths of a second are added first and divided once by 3600, then the three terms are added in 64-bit. A part that is missing or not a number counts as zero.
ifc-geo.js
lines 38 to 43
BimCraft 0.8.1
/** IfcCompoundPlaneAngleMeasure [deg, min, sec, millionths of a second] (all the same sign) → degrees. */
export function compound(v) {
const a = (Array.isArray(v) ? v : []).map((x) => Number(val(x)) || 0);
if (!a.length) return null;
return a[0] + (a[1] || 0) / 60 + ((a[2] || 0) + (a[3] || 0) / 1e6) / 3600;
}
File point to map grid
64-bit
Easting, northing and height in meters of a point of the file.
[E, N, H] = (matrix × p) × meters per map unit
One 4 × 4 matrix product and one multiplication per coordinate in 64-bit. A 64-bit easting of 500 000 m is held in steps of about 6e-11 m.
ifc-geo.js
lines 87 to 91
BimCraft 0.8.1
/** A file point (file units, Z up) → [E, N, H] metres on the map grid. */
export function fileToMap(g, p) {
const q = apply4(g.matrix, p[0], p[1], p[2]);
return [q[0] * g.mapUnitM, q[1] * g.mapUnitM, q[2] * g.mapUnitM];
}
Longitude and latitude from the site's reference point
64-bit
An approximate WGS 84 position of a file point from its offset east and north of the site's point.
w = √(1 − e² sin² φ); N = a ÷ w; M = a (1 − e²) ÷ w³; lon = lon₀ + east ÷ ((N + h) cos φ); lat = lat₀ + north ÷ (M + h)
64-bit throughout, with the WGS 84 defining constants. The offsets are turned by the file's true-north direction and divided by radii evaluated at the reference latitude only, a first-order approximation that the file's header calls fine across a site, not across a city. The division by cos φ is not guarded at the poles.
ifc-geo.js
lines 93 to 100
BimCraft 0.8.1
/** A file point → [lon, lat, h] on WGS 84 from the site's reference point (east / north along TrueNorth, up). */
export function siteLonLat(g, p) {
const u = g.fileUnitM, d = [(p[0] - g.origin[0]) * u, (p[1] - g.origin[1]) * u, (p[2] - g.origin[2]) * u];
const [nx, ny] = g.north, east = d[0] * ny - d[1] * nx, north = d[0] * nx + d[1] * ny;
const phi = g.lat * Math.PI / 180, w = Math.sqrt(1 - E2 * Math.sin(phi) ** 2);
const N = A / w, M = A * (1 - E2) / w ** 3;
return [g.lon + (east / ((N + g.elev) * Math.cos(phi))) * 180 / Math.PI, g.lat + (north / (M + g.elev)) * 180 / Math.PI, g.elev + d[2]];
}
Degrees to degrees, minutes and seconds
64-bit
Formats a latitude or longitude with its hemisphere letter.
d = ⌊|deg|⌋; m = ⌊(|deg| − d) × 60⌋; s = (|deg| − d − m ÷ 60) × 3600, shown with 2 decimals
The seconds are what is left after subtracting the whole degrees and minutes, so they keep fewer significant digits than the angle; the result is shown to 0.01 second. Seconds of 59.995 or more roll over to the next minute so that 60.00 is never printed.
ifc-geo.js
lines 108 to 115
BimCraft 0.8.1
/** Degrees → degrees, minutes, seconds with the hemisphere letter. */
export function dms(deg, axis) {
const h = axis === 'lat' ? (deg < 0 ? 'S' : 'N') : (deg < 0 ? 'W' : 'E'); // i18n-ignore: compass letters as on maps
let a = Math.abs(deg), d = Math.floor(a), m = Math.floor((a - d) * 60), s = (a - d - m / 60) * 3600;
if (s >= 59.995) { s = 0; m += 1; }
if (m >= 60) { m = 0; d += 1; }
return d + '° ' + String(m).padStart(2, '0') + '′ ' + s.toFixed(2).padStart(5, '0') + '″ ' + h;
}
Viewer point to the file's own coordinates
64-bit
Takes a point picked in the viewer back to the file's Z-up coordinates in its own length unit.
y = C⁻¹ × (place⁻¹ × p); file = (y_x, −y_z, y_y) ÷ meters per file unit
Two general 4 × 4 inverses and two matrix products in 64-bit, then one division per coordinate. The point itself comes from the viewer.
ifc-geo.js
lines 117 to 124
BimCraft 0.8.1
/**
* A point in BimCraft's viewer (scene space, metres, Y up) → the file's own coordinates (file units, Z up).
* `place` = the model's scene placement (viewer.modelMatrix), `C` = its coordination matrix (api.GetCoordinationMatrix).
*/
export function viewerToFile(p, place, C, fileUnitM) {
const local = apply4(inv4(place), p[0], p[1], p[2]), y = apply4(inv4(C), local[0], local[1], local[2]); // Y up, metres, where the file has it
return [y[0] / fileUnitM, -y[2] / fileUnitM, y[1] / fileUnitM];
}
In detail
Method
Map: [E, N, H] = matrix × point × map unit. Site: N = a ÷ √(1 − e² sin² φ), M = a (1 − e²) ÷ (1 − e² sin² φ)^1.5, Δlon = east ÷ ((N + h) cos φ), Δlat = north ÷ (M + h).
Number types
64-bit numbers. a = 6378137 m, f = 1 ÷ 298.257223563, e² = f (2 − f).
Tolerances
A true-north direction shorter than 1e-9 is ignored. Seconds of 59.995 or more are carried into the next minute when an angle is formatted.
The site method uses the radii at the reference latitude only and divides by cos φ, so it degrades with distance from the reference point and near the poles. It assumes the site's latitude and longitude are WGS 84.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.