Currently, we support two different coordinate systems for Spaces and one coordinate system for Devices.
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.
Copy {
....
"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"
}
}
}
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 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.
Copy {
...
"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.
Copy {
...
"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 coordinate system (x, y) - floating numbers.
Bounding box is defined as with a range of (0, 0) - (1000, 1000). Which yields the following coordinates:
Copy 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.
TypeScript conversion code
Copy 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
);
}
}
Example 1 - Image where width > height
Copy // 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
Copy // 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
Copy // 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}),
);
Last updated 7 months ago