# How CoordTransformer Converts Geographic Latitude and Longitude to Minecraft XZ Coordinates in Arnis

> Learn how CoordTransformer converts geographic lat/lon to Minecraft XZ coordinates by calculating relative positions and scaling them for accurate block placement in Arnis.

- Repository: [Louis Erbkamm/arnis](https://github.com/louis-e/arnis)
- Tags: how-to-guide
- Published: 2026-03-20

---

**CoordTransformer converts geographic latitude and longitude to Minecraft XZ coordinates by calculating relative positions within a geographic bounding box and scaling them by ground distance factors to produce Minecraft block coordinates.**

Arnis is an open-source project that generates Minecraft worlds from real-world OpenStreetMap data. The `CoordTransformer` module in [`src/coordinate_system/transformation.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/transformation.rs) handles the critical task to convert geographic latitude and longitude to Minecraft XZ coordinates, bridging real-world geographic data with Minecraft's Cartesian plane.

## The Three-Stage Conversion Process

The transformation operates in three distinct stages: initializing the transformer with geographic bounds, computing relative positions and scale factors, and transforming individual points to Minecraft XZ coordinates.

### Stage 1: Initializing the Transformer with Geographic Bounds

The process begins with `CoordTransformer::llbbox_to_xzbbox`, which takes an `LLBBox` (latitude/longitude bounding box) and a user-defined `scale` factor. This method establishes the transformation parameters by:

- Using `geo_distance` to compute the ground distance in meters between the bounding box's opposite corners for both latitude (north-south) and longitude (east-west) axes.
- Multiplying these distances by the `scale` factor to create `scale_factor_x` and `scale_factor_z`, which represent how many Minecraft blocks correspond to real-world meters.
- Storing the bounding box dimensions (`len_lat`, `len_lng`) and minimum corner coordinates (`min_lat`, `min_lng`) for subsequent relative calculations.

```rust
// src/coordinate_system/transformation.rs#L23-L50
pub fn llbbox_to_xzbbox(
    llbbox: &LLBBox,
    scale: f64,
) -> Result<(CoordTransformer, XZBBox), String> {
    let (scale_factor_z, scale_factor_x) = geo_distance(llbbox.min(), llbbox.max());
    let scale_factor_z: f64 = scale_factor_z.floor() * scale;
    let scale_factor_x: f64 = scale_factor_x.floor() * scale;
    // ...
    Ok((
        Self {
            len_lat: llbbox.max().lat() - llbbox.min().lat(),
            len_lng: llbbox.max().lng() - llbbox.min().lng(),
            scale_factor_x,
            scale_factor_z,
            min_lat: llbbox.min().lat(),
            min_lng: llbbox.min().lng(),
        },
        xzbbox,
    ))
}

```

### Stage 2: Computing Relative Positions and Scale Factors

Once the transformer is initialized, it retains the geographic bounds and scale factors. The `scale_factor_x` corresponds to the east-west (longitude) axis, while `scale_factor_z` corresponds to the north-south (latitude) axis. These factors convert real-world meter distances into Minecraft block counts based on the user-provided scale.

### Stage 3: Transforming Individual Points to Minecraft XZ

The `CoordTransformer::transform_point` method converts individual `LLPoint` instances to `XZPoint` coordinates through relative positioning:

1. **Calculate relative position**: Compute where the point lies within the bounding box as a percentage (0.0 to 1.0) of the total width and height.
2. **Invert the Z-axis**: Subtract the latitude percentage from 1.0 because Minecraft's Z-axis increases southward while latitude increases northward.
3. **Apply scale factors**: Multiply the relative positions by `scale_factor_x` and `scale_factor_z` to obtain Minecraft block coordinates.
4. **Convert to integers**: Cast the results to `i32` to produce discrete block positions.

```rust
// src/coordinate_system/transformation.rs#L53-L62
pub fn transform_point(&self, llpoint: LLPoint) -> XZPoint {
    let rel_x: f64 = (llpoint.lng() - self.min_lng) / self.len_lng;
    let rel_z: f64 = 1.0 - (llpoint.lat() - self.min_lat) / self.len_lat;
    let x: i32 = (rel_x * self.scale_factor_x) as i32;
    let z: i32 = (rel_z * self.scale_factor_z) as i32;
    XZPoint::new(x, z)
}

```

## Implementation Details and Code Structure

### The llbbox_to_xzbbox Method

This constructor method in [`src/coordinate_system/transformation.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/transformation.rs) serves as the entry point for creating a coordinate transformer. It validates the geographic bounding box and computes the scale factors using the Haversine-based `geo_distance` function to determine real-world distances between coordinates.

### The transform_point Method

This method performs the actual coordinate conversion. It handles the axis inversion necessary to reconcile geographic coordinate systems (where latitude increases northward) with Minecraft's coordinate system (where Z decreases northward). The method returns an `XZPoint` containing `i32` values suitable for Minecraft block placement.

### Legacy Compatibility Function

The codebase maintains `lat_lon_to_minecraft_coords` as a standalone function for backward compatibility and verification purposes. The test suite asserts that `transform_point` produces identical results to this legacy implementation, ensuring functional equivalence while providing a more object-oriented interface.

```rust
// src/coordinate_system/transformation.rs#L99-L120
pub fn lat_lon_to_minecraft_coords(
    lat: f64,
    lon: f64,
    bbox: LLBBox,
    scale_factor_z: f64,
    scale_factor_x: f64,
) -> (i32, i32) {
    // Legacy implementation for comparison
}

```

## Practical Usage Example

The following example demonstrates how to initialize a `CoordTransformer` and convert geographic coordinates to Minecraft XZ coordinates:

```rust
use arnis::coordinate_system::{
    geographic::{LLBBox, LLPoint},
    transformation::CoordTransformer,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Define the geographic area to map (e.g., Munich city centre)
    let llbbox = LLBBox::new(
        LLPoint::new(48.1351, 11.5760)?, // min lat, min lng
        LLPoint::new(48.1551, 11.5960)?, // max lat, max lng
    )?;

    // Build a transformer with a desired scale (1 Minecraft block ≈ 1 metre)
    let scale = 1.0;
    let (transformer, _xz_bbox) = CoordTransformer::llbbox_to_xzbbox(&llbbox, scale)?;

    // Convert a latitude/longitude inside the bbox to Minecraft X‑Z
    let point = LLPoint::new(48.1450, 11.5860)?;
    let xz = transformer.transform_point(point);

    println!("Minecraft coordinates: X = {}, Z = {}", xz.x, xz.z);
    Ok(())
}

```

You can also verify the results using the legacy helper function:

```rust
let (scale_factor_z, scale_factor_x) = arnis::coordinate_system::transformation::geo_distance(
    llbbox.min(),
    llbbox.max(),
);
let (x, z) = arnis::coordinate_system::transformation::lat_lon_to_minecraft_coords(
    point.lat(),
    point.lng(),
    llbbox,
    scale_factor_z.floor() * scale,
    scale_factor_x.floor() * scale,
);
assert_eq!((xz.x, xz.z), (x, z));

```

## Key Source Files

| File | Role |
|------|------|
| [`src/coordinate_system/transformation.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/transformation.rs) | Contains `CoordTransformer`, `llbbox_to_xzbbox`, `transform_point`, distance helpers, and the legacy conversion function. |
| [`src/coordinate_system/geographic/llpoint.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/geographic/llpoint.rs) | Represents a single latitude/longitude point (`LLPoint`). |
| [`src/coordinate_system/geographic/llbbox.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/geographic/llbbox.rs) | Bounding-box wrapper (`LLBBox`) used to define the geographic extents. |
| [`src/coordinate_system/cartesian/xzpoint.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/cartesian/xzpoint.rs) | Simple struct for Minecraft X‑Z coordinates (`XZPoint`). |
| [`src/coordinate_system/cartesian/xzbbox/mod.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/cartesian/xzbbox/mod.rs) | Defines the Minecraft‑world bounding box (`XZBBox`). |

These components together enable **Arnis** to map real-world map data onto Minecraft worlds with precise, reproducible coordinate transformations.

## Summary

- **CoordTransformer** serves as the core engine to convert geographic latitude and longitude to Minecraft XZ coordinates in the Arnis project.
- The **three-stage process** involves initializing the transformer with a geographic bounding box, computing ground distance scale factors using `geo_distance`, and applying relative positioning calculations via `transform_point`.
- **Axis inversion** is handled automatically: latitude increases northward in geographic coordinates, but the Z-axis increases southward in Minecraft, requiring the `1.0 - relative_lat` calculation.
- **`transform_point`** returns `i32` block coordinates directly suitable for Minecraft world generation.
- **Legacy compatibility** is maintained through `lat_lon_to_minecraft_coords`, which validates the modern implementation against historical behavior.

## Frequently Asked Questions

### How does CoordTransformer handle the scale factor between real-world meters and Minecraft blocks?

The scale factor is applied during the initialization phase in `llbbox_to_xzbbox`. The method calculates the ground distance in meters between the geographic bounding box corners using `geo_distance`, then multiplies these distances by the user-provided `scale` parameter to produce `scale_factor_x` and `scale_factor_z`. A scale of `1.0` means one Minecraft block equals approximately one real-world meter.

### Why does the Z coordinate calculation subtract the relative latitude from 1.0?

Minecraft's coordinate system defines the Z-axis as positive toward the south, while geographic latitude increases toward the north. To reconcile these opposing directions, `transform_point` calculates the relative position as `(lat - min_lat) / len_lat`, then inverts it with `1.0 - relative_lat`. This ensures that northern geographic coordinates map to lower Z values in Minecraft, maintaining correct directional orientation.

### Can I use the legacy conversion function instead of CoordTransformer?

Yes, the legacy function `lat_lon_to_minecraft_coords` remains available in [`src/coordinate_system/transformation.rs`](https://github.com/louis-e/arnis/blob/main/src/coordinate_system/transformation.rs) for backward compatibility. However, the modern `CoordTransformer` approach is recommended because it encapsulates the bounding box and scale factors within a reusable struct, eliminating the need to pass these parameters repeatedly for each point conversion. The test suite verifies that both methods produce identical results.