# Crosstech Errors Coordinate Mismatch

> >

- **Type:** Skill
- **Install:** `agentstack add skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-errors-coordinate-mismatch`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Impertio-Studio](https://agentstack.voostack.com/s/impertio-studio)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Impertio-Studio](https://github.com/Impertio-Studio)
- **Source:** https://github.com/Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package/tree/main/skills/source/crosstech-errors/crosstech-errors-coordinate-mismatch

## Install

```sh
agentstack add skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-errors-coordinate-mismatch
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# crosstech-errors-coordinate-mismatch

## Quick Reference

| Symptom | Likely Cause | Jump To |
|---------|-------------|---------|
| Model rotated 90° / lying on side | Y-up vs Z-up axis swap | [Failure Mode 1](#failure-mode-1-y-up-vs-z-up-axis-swap) |
| Model 1000x too large or too small | Unit mismatch (mm/m/ft) | [Failure Mode 2](#failure-mode-2-unit-mismatch-mmmft) |
| Model at (0°N, 0°E) in Gulf of Guinea | Missing georeferencing | [Failure Mode 3](#failure-mode-3-missing-georeferencing) |
| Model offset by hundreds of km | Wrong CRS / EPSG code | [Failure Mode 4](#failure-mode-4-wrong-crs--epsg-code) |
| Building rotated on map, footprint correct | True North rotation error | [Failure Mode 5](#failure-mode-5-true-north-rotation-error) |
| Coordinates jump at project boundary | UTM zone boundary | [Failure Mode 6](#failure-mode-6-utm-zone-boundary) |
| Building floating ~43m above terrain | Vertical datum confusion | [Failure Mode 7](#failure-mode-7-vertical-datum-confusion) |

---

## Axis Convention Table

| Tool | Up Axis | Handedness | Default Units | Coordinate Order |
|------|---------|-----------|---------------|-----------------|
| **Blender** | Z-up | Right-handed | Meters | (X, Y, Z) |
| **Three.js** | Y-up | Right-handed | Unitless | (X, Y, Z) |
| **QGIS** | Z-up (2.5D) | Right-handed | Map units (m/deg) | (X=East, Y=North) |
| **IFC** | Z-up | Right-handed | Project units (mm/m) | (X, Y, Z) |
| **Revit** | Z-up | Right-handed | Internal: feet | (X, Y, Z) |
| **FreeCAD** | Z-up | Right-handed | Internal: mm | (X, Y, Z) |
| **Speckle** | Z-up | Right-handed | Meters | (X, Y, Z) |
| **web-ifc** | Z-up | Right-handed | IFC project units | (X, Y, Z) |

**Key boundary**: ALL BIM/GIS tools use Z-up. ONLY web 3D renderers (Three.js, Babylon.js) use Y-up.

---

## Diagnostic Decision Tree

```
START: Model appears wrong in target application
│
├─ Is the model rotated 90°?
│  YES → Failure Mode 1 (Y/Z axis swap)
│
├─ Is the model at the correct location but wrong size?
│  ├─ ~1000x too large → IFC in mm, target expects m (Scale missing)
│  ├─ ~3.28x too large → IFC in feet, target expects m
│  ├─ ~0.3x too small  → IFC in m, target expects feet
│  └─ YES → Failure Mode 2 (unit mismatch)
│
├─ Is the model near (0, 0) / Gulf of Guinea?
│  YES → Failure Mode 3 (missing georeferencing)
│
├─ Is the model offset by >1 km from expected position?
│  ├─ Offset ~100–500 km → Failure Mode 4 (wrong CRS)
│  ├─ Offset ~500,000 m in easting → Failure Mode 6 (UTM zone)
│  └─ Offset  1.0) {
    console.error("Y/Z axis swap detected: model is lying flat");
}
```

### Fix

```javascript
// JavaScript: BIM Z-up → Three.js Y-up
function bimToThreeJs(x, y, z) {
    return { x: x, y: z, z: -y };
}
```

The negation of `y` → `-z` preserves right-handedness. Without negation, the model appears mirrored.

**ALWAYS** apply this transform when loading IFC/BIM data into Three.js or Babylon.js.

**NEVER** apply this transform when loading into Blender, QGIS, or other Z-up tools.

### Tool-Specific Notes

- **web-ifc / ThatOpen Components**: Handles Y/Z swap automatically when using `FragmentsManager`. Manual loading via `IfcLoader` requires explicit swap.
- **Three.js GLTFLoader**: glTF is Y-up by spec — no swap needed for glTF files. Only swap for raw IFC coordinate data.
- **Speckle**: Converts axes automatically per target application on receive.

---

## Failure Mode 2: Unit Mismatch (mm/m/ft)

### Symptom
Model appears at the correct location but is absurdly large or small. A 10m building spans 10km on the map (mm→m) or appears as a 3m speck (feet→m without conversion).

### Detection

```python
import ifcopenshell

ifc = ifcopenshell.open("model.ifc")

# Step 1: Read project length units
units = ifc.by_type("IfcUnitAssignment")[0]
for unit in units.Units:
    if hasattr(unit, "UnitType") and unit.UnitType == "LENGTHUNIT":
        if hasattr(unit, "Prefix") and unit.Prefix == "MILLI":
            project_unit = "mm"
            expected_scale = 0.001
        elif hasattr(unit, "Name") and unit.Name == "FOOT":
            project_unit = "ft"
            expected_scale = 0.3048
        else:
            project_unit = "m"
            expected_scale = 1.0

# Step 2: Read IfcMapConversion Scale
map_conv = ifc.by_type("IfcMapConversion")
if map_conv:
    actual_scale = map_conv[0].Scale or 1.0  # None defaults to 1.0
    if abs(actual_scale - expected_scale) > 0.0001:
        print(f"SCALE MISMATCH: project={project_unit}, "
              f"MapConversion.Scale={actual_scale}, expected={expected_scale}")
```

### Common Scale Factors

| From | To | Scale Factor |
|------|----|-------------|
| mm | m | 0.001 |
| m | m | 1.0 |
| ft | m | 0.3048 |
| in | m | 0.0254 |

### Fix
Set `IfcMapConversion.Scale` to the correct conversion factor from project units to CRS map units.

**ALWAYS** check `IfcUnitAssignment` before interpreting `IfcMapConversion.Scale`.

**NEVER** assume project units are meters — Revit uses feet internally, FreeCAD uses mm.

---

## Failure Mode 3: Missing Georeferencing

### Symptom
Model appears at latitude 0°, longitude 0° (Gulf of Guinea, West Africa) or at the CRS origin point. This is the single most common BIM↔GIS integration failure.

### Detection

```python
import ifcopenshell

ifc = ifcopenshell.open("model.ifc")
has_map_conv = len(ifc.by_type("IfcMapConversion")) > 0
has_crs = len(ifc.by_type("IfcProjectedCRS")) > 0

if not has_map_conv:
    print("MISSING: IfcMapConversion — no coordinate transformation defined")
if not has_crs:
    print("MISSING: IfcProjectedCRS — no target CRS defined")
```

### Fix
Add georeferencing using IfcOpenShell:

```python
import ifcopenshell
import ifcopenshell.api

ifc = ifcopenshell.open("model.ifc")

# Add georeferencing entities
ifcopenshell.api.run("georeference.add_georeferencing", ifc)

# Set CRS and map conversion parameters
ifcopenshell.api.run("georeference.edit_georeferencing", ifc,
    projected_crs={"Name": "EPSG:28992"},
    coordinate_operation={
        "Eastings": 155000.0,
        "Northings": 463000.0,
        "OrthogonalHeight": 0.0,
    })

ifc.write("model_georef.ifc")
```

**ALWAYS** verify Eastings/Northings fall within the valid range of the target CRS.

**NEVER** leave IfcProjectedCRS.Name empty — downstream tools rely on this field to resolve the CRS.

---

## Failure Mode 4: Wrong CRS / EPSG Code

### Symptom
Model appears offset by tens to hundreds of kilometers from the expected position.

### Detection
Validate that coordinate values are plausible for the assigned CRS:

| CRS | Valid Easting Range | Valid Northing Range |
|-----|-------------------|---------------------|
| EPSG:28992 (RD New) | -7,000 to 300,000 | 289,000 to 629,000 |
| EPSG:32631 (UTM 31N) | 166,000 to 834,000 | 0 to 9,400,000 |
| EPSG:32632 (UTM 32N) | 166,000 to 834,000 | 0 to 9,400,000 |

```python
import ifcopenshell

ifc = ifcopenshell.open("model.ifc")
map_conv = ifc.by_type("IfcMapConversion")
crs = ifc.by_type("IfcProjectedCRS")

if map_conv and crs:
    e = map_conv[0].Eastings
    n = map_conv[0].Northings
    crs_name = crs[0].Name

    # Example: validate RD New range
    if "28992" in (crs_name or ""):
        if not (-7000  0.001:
        print(f"WARNING: direction vector not normalized (length={vec_length:.4f})")
```

### Fix
Set `XAxisAbscissa = cos(angle)` and `XAxisOrdinate = sin(angle)` where `angle` is the anti-clockwise rotation from grid north (CRS Y-axis) to project north.

**ALWAYS** verify the direction vector is normalized (length = 1.0).

**NEVER** confuse the sign convention — positive angle = anti-clockwise rotation in IFC.

---

## Failure Mode 6: UTM Zone Boundary

### Symptom
Project straddles a UTM zone boundary. Coordinates jump by ~500,000m in easting, or measurements near the boundary show 0.04% distortion.

### Detection

```python
from pyproj import CRS

def utm_zone_from_longitude(lon):
    """Return UTM zone number for a given longitude."""
    return int((lon + 180) / 6) + 1

# Example: Netherlands straddles UTM zones 31 and 32
# Zone 31N: 0°E to 6°E (central meridian 3°E)
# Zone 32N: 6°E to 12°E (central meridian 9°E)
western_nl_zone = utm_zone_from_longitude(4.5)   # Zone 31
eastern_nl_zone = utm_zone_from_longitude(6.5)   # Zone 32
```

### Fix
**NEVER** mix coordinates from different UTM zones in one project.

Use a single CRS that covers the entire project area:
- Netherlands: EPSG:28992 (RD New) covers the entire country
- Europe-wide: EPSG:3035 (ETRS89 / LAEA) for pan-European projects
- If UTM is required, pick the zone containing the project centroid

---

## Failure Mode 7: Vertical Datum Confusion

### Symptom
Model floats ~42–44 meters above the terrain (Netherlands) or is buried below ground. Horizontal position is correct.

### Cause
Mixing NAP (Normaal Amsterdams Peil) orthometric heights with WGS84 ellipsoidal heights. In the Netherlands, the geoid undulation (difference between ellipsoid and geoid) is approximately 42–44 meters.

### Detection

```python
import ifcopenshell

ifc = ifcopenshell.open("model.ifc")
crs = ifc.by_type("IfcProjectedCRS")
map_conv = ifc.by_type("IfcMapConversion")

if crs:
    vert_datum = crs[0].VerticalDatum
    if vert_datum is None:
        print("WARNING: No vertical datum specified — height interpretation ambiguous")

if map_conv:
    h = map_conv[0].OrthogonalHeight
    if abs(h) > 40 and abs(h) < 50:
        print(f"SUSPICIOUS: OrthogonalHeight={h}m — possible NAP↔ellipsoid confusion")
```

### Fix

```python
from pyproj import Transformer

# Transform NAP height to WGS84 ellipsoidal height (or vice versa)
# Requires PROJ grid files (nl_nsgi_nlgeo2018.tif)
t = Transformer.from_crs(
    "EPSG:7415",   # RD New + NAP (compound)
    "EPSG:4979",   # WGS84 3D (geographic + ellipsoidal height)
    always_xy=True
)
lon, lat, h_ellipsoid = t.transform(easting, northing, h_nap)
geoid_undulation = h_ellipsoid - h_nap  # ~42-44m in NL
```

**ALWAYS** specify `IfcProjectedCRS.VerticalDatum` (e.g., "NAP") to make height interpretation unambiguous.

**NEVER** assume heights are ellipsoidal — most BIM models use orthometric (above sea level) heights.

---

## Critical Rules

1. **ALWAYS** check axis conventions before crossing a tool boundary — Z-up to Y-up swaps are the most common error.
2. **ALWAYS** verify `IfcMapConversion.Scale` matches the ratio of project units to CRS units.
3. **ALWAYS** use `always_xy=True` when creating pyproj Transformers.
4. **ALWAYS** validate coordinate ranges against the assigned CRS before declaring georeferencing correct.
5. **NEVER** assume georeferencing exists — explicitly check for `IfcMapConversion` and `IfcProjectedCRS`.
6. **NEVER** mix coordinates from different UTM zones in a single project.
7. **NEVER** assume heights are ellipsoidal — BIM models use orthometric heights by default.
8. **NEVER** skip the direction vector normalization check for True North rotation.
9. **ALWAYS** check for compound errors — multiple failure modes can occur simultaneously.
10. **ALWAYS** verify fixes with a known reference point before declaring the issue resolved.

---

## Reference Links

- [references/methods.md](references/methods.md) — Diagnostic API signatures for coordinate issues per tool
- [references/examples.md](references/examples.md) — Real coordinate error scenarios with step-by-step fixes
- [references/anti-patterns.md](references/anti-patterns.md) — Coordinate handling mistakes to avoid

### Official Sources

- https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcmapconversion.htm
- https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcprojectedcrs.htm
- https://pyproj4.github.io/pyproj/stable/api/transformer.html
- https://ifcopenshell.github.io/docs/python/html/ifcopenshell-python/geometry_processing.html
- https://epsg.io/

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Impertio-Studio](https://github.com/Impertio-Studio)
- **Source:** [Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package](https://github.com/Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-errors-coordinate-mismatch
- Seller: https://agentstack.voostack.com/s/impertio-studio
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
