# Coordinate systems

Currently, we support two different coordinate systems for Spaces and one coordinate system for Devices.

* [Real coordinates](#real-coordinates) - Applicable to Spaces of type Building.
* [Virtual coordinates](#virtual-coordinates) - Applicable to Devices and Spaces that are not of type Building.

## Real coordinates

Real coordinates is only applicable Spaces of type Building, and represent the lat / lon location of the building. This is specified in the GeoJSON coordinates as \[latitude, longtitude, altitude], where the altitude is optional and ignored.

{% code title="Example Building Space" %}

```json
{
  ....
  "type": "SPACE_BUILDING",
  "coordinates": "COORDS_REAL",
  "image": "https://app.neowit.io/api/image/v1/image/<someimage>",
  "features": {
    "type": "Feature",
    "geometry": {
      "type": "Point",
      "coordinates": [
        -74.0088256,
        40.7060361,
        0
      ]
    },
    "properties": {
      "address": "Wall St, New York, NY, USA"
    }
  }
}
```

{% endcode %}

## Virtual coordinates

The virtual coordinate system is a bit difficult to work with, as it is tightly coupled with our web app and its [deck.gl](https://deck.gl/) usage. In the future, we will introduce a better coordinate system that is easier to work with and maps better to real word coordinates.

### Spaces

All Spaces except Buildings are defined using a GeoJSON Feature using Polygon geometry.

```json
{
    ...
    "name": "My Room",
    "type": "SPACE_ROOM",
    "coordinates": "COORDS_VIRTUAL",
    "image": "https://app.neowit.io/api/image/v1/image/<id>.png",
    "features": {
        "type": "Feature",
        "geometry": {
            "type": "Polygon",
            "coordinates": [
                [
                    [
                        796.5946119869824,
                        908.4491380427061
                    ],
                    [
                        796.5946119869824,
                        717.0977754704593
                    ],
                    [
                        987.9459745592292,
                        717.0977754704593
                    ],
                    [
                        987.9459745592292,
                        908.4491380427061
                    ],
                    [
                        796.5946119869824,
                        908.4491380427061
                    ]
                ]
            ]
        },
        "properties": {
            "shape": "Rectangle"
        }
    }
}
```

### Devices

Devices may be given a position using a GeoJSON Feature using Point geometry. Note that this only makes sense if the Device has an associated Space.

```json
{
    ...
    "name": "A Device",
    "spaceId": "<associatedSpaceId>",
    "features": {
        "type": "Feature",
        "geometry": {
            "type": "Point",
            "coordinates": [
                531.0645562037637,
                11.737089201877893
            ]
        },
        "properties": null
    }
}
```

### Basic workings of the coordinate system

* 2-dimensional [Cartesian](https://en.wikipedia.org/wiki/Cartesian_coordinate_system) coordinate system (x, y) - floating numbers.
* Bounding box is defined as with a range of (0, 0) - (1000, 1000). Which yields the following coordinates:

```
   top: left(0, 1000) right(1000, 1000)
bottom: left(0,    0) right(1000,    0)
```

* The image uploaded as part of the Floor is preserved in its full size, but we do an object fit into the bounding box, preserving its aspect ratio, when mapping it into the coordinate system. Origo of the object fit is Cartesian (x, y) = (0, 0).
* Coordinates outside of the \[0, 1000] range are permitted for historic reasons.

### Converting between image pixel and Cartesian

Example conversion code for TypeScript is show below. This is a small utility class to convert between Cartesian and pixel coordinates defined by the uploaded image.

{% code title="TypeScript conversion code" lineNumbers="true" %}

```typescript
type Size = {
	width: number;
	height: number;
};

type Point = {
	x: number;
	y: number;
};

type Domain = {
	max: number;
	min: number;
};

export class Convert {
	private readonly BOX_SIZE = 1000;

	private size: Size;

	private box: Size;

	constructor(size: Size) {
		this.size = size;
		this.box = this.pixelSizeToCartesian(size);
	}

	pixelPointToCartesian(pixel: Point): Point {
		const x = Convert.normalize(
			pixel.x,
			{min: 0, max: this.size.width},
			{min: 0, max: this.box.width},
		);
		const y = Convert.normalize(
			this.size.height - pixel.y,
			{min: 0, max: this.size.height},
			{min: 0, max: this.box.height},
		);
		return {x, y};
	}

	cartesianPointToPixel(coords: Point): Point {
		const x = Convert.normalize(
			coords.x,
			{min: 0, max: this.box.width},
			{min: 0, max: this.size.width},
		);
		const y =
			this.size.height -
			Convert.normalize(
				coords.y,
				{min: 0, max: this.box.height},
				{min: 0, max: this.size.height},
			);
		return {x, y};
	}

	pixelSizeToCartesian(size: Size): Size {
		const ratio = size.height / size.width;
		const width = ratio >= 1 ? this.BOX_SIZE / ratio : this.BOX_SIZE;
		const height = ratio >= 1 ? this.BOX_SIZE : this.BOX_SIZE * ratio;
		return {width, height};
	}

	private static normalize(
		input: number,
		inDomain: Domain,
		outDomain: Domain,
	): number {
		const inputRange = inDomain.max - inDomain.min;
		const outputRange = outDomain.max - outDomain.min;
		return (
			((input - inDomain.min) / inputRange) * outputRange + outDomain.min
		);
	}
}
```

{% endcode %}

#### Example 1 - Image where width > height

```typescript
// Image size (WxH) = (768x576)
// .----------------------------------------------------.
// | orientation  | image pixel (x,y) | cartesian (x,y) |
// |--------------|-------------------|-----------------|
// | left bottom  |   (  0, 576)      | (   0,   0)     |
// | left top     |   (  0,   0)      | (   0, 750)     |
// | right bottom |   (768, 576)      | (1000,   0)     | 
// | right top    |   (768,   0)      | (1000, 750)     |
// .----------------------------------------------------.
const c = new Convert({width: 768, height: 576});
console.log(
	'left  bottom PIXEL(  0,576) => (   0,   0)',
	c.cartesianPointToPixel({x: 0, y: 0}),
	c.pixelPointToCartesian({x: 0, y: 576}),
);
console.log(
	'left  top    PIXEL(  0,  0) => (   0, 750)',
	c.cartesianPointToPixel({x: 0, y: 750}),
	c.pixelPointToCartesian({x: 0, y: 0}),
);
console.log(
	'right bottom PIXEL(768,576) => (1000,   0)',
	c.cartesianPointToPixel({x: 1000, y: 0}),
	c.pixelPointToCartesian({x: 768, y: 576}),
);
console.log(
	'right top    PIXEL(768,  0) => (1000, 750)',
	c.cartesianPointToPixel({x: 1000, y: 750}),
	c.pixelPointToCartesian({x: 768, y: 0}),
);
```

#### Example 2 - Image where width < height

```typescript
// Image size (WxH) = (576x768)
// .----------------------------------------------------.
// | orientation  | image pixel (x,y) | cartesian (x,y) |
// |--------------|-------------------|-----------------|
// | left bottom  |   (  0, 768)      | (  0,    0)     |
// | left top     |   (  0,   0)      | (  0, 1000)     |
// | right bottom |   (576, 768)      | (750,    0)     |
// | right top    |   (576,   0)      | (750, 1000)     |
// .----------------------------------------------------.
const c = new Convert({width: 576, height: 768});
console.log(
	'left  bottom PIXEL(  0,768) => (  0,    0)',
	c.cartesianPointToPixel({x: 0, y: 0}),
	c.pixelPointToCartesian({x: 0, y: 768}),
);
console.log(
	'left  top    PIXEL(  0,  0) => (  0, 1000)',
	c.cartesianPointToPixel({x: 0, y: 1000}),
	c.pixelPointToCartesian({x: 0, y: 0}),
);
console.log(
	'right bottom PIXEL(576,768) => (750,    0)',
	c.cartesianPointToPixel({x: 750, y: 0}),
	c.pixelPointToCartesian({x: 576, y: 768}),
);
console.log(
	'right top    PIXEL(576,  0) => (750, 1000)',
	c.cartesianPointToPixel({x: 750, y: 1000}),
	c.pixelPointToCartesian({x: 576, y: 0}),
);
```

#### Example 3 - Image where width == height

```typescript
// Image size (WxH) = (768x768)
// .----------------------------------------------------.
// | orientation  | image pixel (x,y) | cartesian (x,y) |
// |--------------|-------------------|-----------------|
// | left bottom  |   (  0, 768)      | (   0,    0)    |
// | left top     |   (  0,   0)      | (   0, 1000)    |
// | right bottom |   (768, 768)      | (1000,    0)    |
// | right top    |   (768,   0)      | (1000, 1000)    |
// .----------------------------------------------------.
const c = new Convert({width: 768, height: 768});
console.log(
	'left  bottom PIXEL(  0,768) => (   0,    0)',
	c.cartesianPointToPixel({x: 0, y: 0}),
	c.pixelPointToCartesian({x: 0, y: 768}),
);
console.log(
	'left  top    PIXEL(  0,  0) => (   0, 1000)',
	c.cartesianPointToPixel({x: 0, y: 1000}),
	c.pixelPointToCartesian({x: 0, y: 0}),
);
console.log(
	'right bottom PIXEL(768,768) => (1000,    0)',
	c.cartesianPointToPixel({x: 1000, y: 0}),
	c.pixelPointToCartesian({x: 768, y: 768}),
);
console.log(
	'right top    PIXEL(768,  0) => (1000, 1000)',
	c.cartesianPointToPixel({x: 1000, y: 1000}),
	c.pixelPointToCartesian({x: 768, y: 0}),
);
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.neowit.io/rest-api/coordinate-systems.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
