Placing features on a map in the correct geographic coordinates requires precision and accuracy to match the real world locations. We’ve created a data structure in Unity for geo coordinates to match Unity World Space with the real world. Here is a preview of what is shipping in our latest Mapbox Unity SDK release.
Unity’s coordinate system and math is based on 32bit floats, which causes floating point precision issues when working at a global scale. For example, the longitude of San Francisco is around -122.450475 will round to -122.4505 as a 32bit float. Such rounding can cause up to a 0.00005 degree error in longitude, which is approximately 4.3 meters (assuming the same latitude) loss. This loss is smaller in latitude as the range exists in 90 degrees instead of 180 degrees. Because of this, it has one more digit for decimal part providing maximum loss of approximately 0.5 meters. To solve for this, we implemented a double based Vector2 struct called Vector2d. Within the Mapbox Unity SDK geo coordinates are stored as 64bit values to maintain precision.
Relative positioning reduces precision loss
Now that we have the correct longitude and latitude values we have to convert our WGS84 coordinates into a point in Unity space. For this, we use Spherical Mercator EPSG:900913. This conversion projects the longitude and latitude onto planar coordinates which can easily be position in Unity on a plane. At this point, our Spherical Mercator coordinates are still stored as 64bit doubles and we will again lose precision when we try to put those values into a Unity GameObject’s transform which is a 32bit float based Vector2 class. To resolve this, we use relative positioning. The center of the very first tile loaded is moved to Unity World Space origin and used as a reference for every other tile and object we create for a map. For example; the tile containing SF City Hall is at (4548003.18297,-13627499.15079) and if we calculate previous point (4549226.17542,-13630556.63193) relative to this point; it becomes (-1222.99245, 3057.48114) which can be converted to float with a much smaller precision loss.
Rendering continent-sized areas
But this raises another question; what if the initial tile itself is too big for the float range? For example; if you render the entire continent of North America in zoom level one, your tile size will be (20037508.34279, 20037508.34279) and you’ll start having problems if, let’s say, you want to place a cube on each corner of this tile.
A solution we used in our demos is to simply scale down the entire world. Scaling by a factor of 1/1000 for example, you can have a reasonable sized world to play with. This, however, will result in loss of precision. You can use the the size of the features such as buildings or terrain you want to include in your project to determine how much precision loss you can have in your project. You can alter the scale factor dynamically by using the TileSize property in the MapController in the demos.
Grab our latest SDK to use these new features or check out our GitHub repo to try this out for yourself.