You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
mapbox-sdk/Unity/Utilities/Conversions.cs

438 lines
17 KiB

//-----------------------------------------------------------------------
// <copyright file="Conversions.cs" company="Mapbox">
// Copyright (c) 2016 Mapbox. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace Mapbox.Unity.Utilities
{
using Mapbox.Map;
using System;
using Mapbox.Utils;
using UnityEngine;
using System.Globalization;
using Mapbox.Unity.MeshGeneration.Data;
/// <summary>
/// A set of Geo and Terrain Conversion utils.
/// </summary>
public static class Conversions
{
private const int TileSize = 256;
/// <summary>according to https://wiki.openstreetmap.org/wiki/Zoom_levels</summary>
private const int EarthRadius = 6378137; //no seams with globe example
private const double InitialResolution = 2 * Math.PI * EarthRadius / TileSize;
private const double OriginShift = 2 * Math.PI * EarthRadius / 2;
/// <summary>
/// Converts <see cref="T:Mapbox.Utils.Vector2d"/> struct, WGS84
/// lat/lon to Spherical Mercator EPSG:900913 xy meters.
/// </summary>
/// <param name="v"> The <see cref="T:Mapbox.Utils.Vector2d"/>. </param>
/// <returns> A <see cref="T:UnityEngine.Vector2d"/> of coordinates in meters. </returns>
public static Vector2d LatLonToMeters(Vector2d v)
{
return LatLonToMeters(v.x, v.y);
}
/// <summary>
/// Convert a simple string to a latitude longitude.
/// Expects format: latitude, longitude
/// </summary>
/// <returns>The lat/lon as Vector2d.</returns>
/// <param name="s">string.</param>
public static Vector2d StringToLatLon(string s)
{
var latLonSplit = s.Split(',');
if (latLonSplit.Length != 2)
{
throw new ArgumentException("Wrong number of arguments");
}
double latitude = 0;
double longitude = 0;
if (!double.TryParse(latLonSplit[0], NumberStyles.Any, NumberFormatInfo.InvariantInfo, out latitude))
{
throw new Exception(string.Format("Could not convert latitude to double: {0}", latLonSplit[0]));
}
if (!double.TryParse(latLonSplit[1], NumberStyles.Any, NumberFormatInfo.InvariantInfo, out longitude))
{
throw new Exception(string.Format("Could not convert longitude to double: {0}", latLonSplit[0]));
}
return new Vector2d(latitude, longitude);
}
/// <summary>
/// Converts WGS84 lat/lon to Spherical Mercator EPSG:900913 xy meters.
/// SOURCE: http://stackoverflow.com/questions/12896139/geographic-coordinates-converter.
/// </summary>
/// <param name="lat"> The latitude. </param>
/// <param name="lon"> The longitude. </param>
/// <returns> A <see cref="T:UnityEngine.Vector2d"/> of xy meters. </returns>
public static Vector2d LatLonToMeters(double lat, double lon)
{
var posx = lon * OriginShift / 180;
var posy = Math.Log(Math.Tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
posy = posy * OriginShift / 180;
return new Vector2d(posx, posy);
}
/// <summary>
/// Converts WGS84 lat/lon to x/y meters in reference to a center point
/// </summary>
/// <param name="lat"> The latitude. </param>
/// <param name="lon"> The longitude. </param>
/// <param name="refPoint"> A <see cref="T:UnityEngine.Vector2d"/> center point to offset resultant xy, this is usually map's center mercator</param>
/// <param name="scale"> Scale in meters. (default scale = 1) </param>
/// <returns> A <see cref="T:UnityEngine.Vector2d"/> xy tile ID. </returns>
/// <example>
/// Converts a Lat/Lon of (37.7749, 122.4194) into Unity coordinates for a map centered at (10,10) and a scale of 2.5 meters for every 1 Unity unit
/// <code>
/// var worldPosition = Conversions.GeoToWorldPosition(37.7749, 122.4194, new Vector2d(10, 10), (float)2.5);
/// // worldPosition = ( 11369163.38585, 34069138.17805 )
/// </code>
/// </example>
public static Vector2d GeoToWorldPosition(double lat, double lon, Vector2d refPoint, float scale = 1)
{
var posx = lon * OriginShift / 180;
var posy = Math.Log(Math.Tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
posy = posy * OriginShift / 180;
return new Vector2d((posx - refPoint.x) * scale, (posy - refPoint.y) * scale);
}
public static Vector2d GeoToWorldPosition(Vector2d latLong, Vector2d refPoint, float scale = 1)
{
return GeoToWorldPosition(latLong.x, latLong.y, refPoint, scale);
}
public static Vector3 GeoToWorldGlobePosition(double lat, double lon, float radius)
{
double xPos = (radius) * Math.Cos(Mathf.Deg2Rad * lat) * Math.Cos(Mathf.Deg2Rad * lon);
double zPos = (radius) * Math.Cos(Mathf.Deg2Rad * lat) * Math.Sin(Mathf.Deg2Rad * lon);
double yPos = (radius) * Math.Sin(Mathf.Deg2Rad * lat);
return new Vector3((float)xPos, (float)yPos, (float)zPos);
}
public static Vector3 GeoToWorldGlobePosition(Vector2d latLong, float radius)
{
return GeoToWorldGlobePosition(latLong.x, latLong.y, radius);
}
public static Vector2d GeoFromGlobePosition(Vector3 point, float radius)
{
float latitude = Mathf.Asin(point.y / radius);
float longitude = Mathf.Atan2(point.z, point.x);
return new Vector2d(latitude * Mathf.Rad2Deg, longitude * Mathf.Rad2Deg);
}
/// <summary>
/// Converts Spherical Mercator EPSG:900913 in xy meters to WGS84 lat/lon.
/// Inverse of LatLonToMeters.
/// </summary>
/// <param name="m"> A <see cref="T:UnityEngine.Vector2d"/> of coordinates in meters. </param>
/// <returns> The <see cref="T:Mapbox.Utils.Vector2d"/> in lat/lon. </returns>
/// <example>
/// Converts EPSG:900913 xy meter coordinates to lat lon
/// <code>
/// var worldPosition = new Vector2d (4547675.35434,13627665.27122);
/// var latlon = Conversions.MetersToLatLon(worldPosition);
/// // latlon = ( 37.77490, 122.41940 )
/// </code>
/// </example>
public static Vector2d MetersToLatLon(Vector2d m)
{
var vx = (m.x / OriginShift) * 180;
var vy = (m.y / OriginShift) * 180;
vy = 180 / Math.PI * (2 * Math.Atan(Math.Exp(vy * Math.PI / 180)) - Math.PI / 2);
return new Vector2d(vy, vx);
}
/// <summary>
/// Gets the xy tile ID from Spherical Mercator EPSG:900913 xy coords.
/// </summary>
/// <param name="m"> <see cref="T:UnityEngine.Vector2d"/> XY coords in meters. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> A <see cref="T:UnityEngine.Vector2d"/> xy tile ID. </returns>
///
/// <example>
/// Converts EPSG:900913 xy meter coordinates to web mercator tile XY coordinates at zoom 12.
/// <code>
/// var meterXYPosition = new Vector2d (4547675.35434,13627665.27122);
/// var tileXY = Conversions.MetersToTile (meterXYPosition, 12);
/// // tileXY = ( 655, 2512 )
/// </code>
/// </example>
public static Vector2 MetersToTile(Vector2d m, int zoom)
{
var p = MetersToPixels(m, zoom);
return PixelsToTile(p);
}
/// <summary>
/// Gets the tile bounds in Spherical Mercator EPSG:900913 meters from an xy tile ID.
/// </summary>
/// <param name="tileCoordinate"> XY tile ID. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> A <see cref="T:UnityEngine.Rect"/> in meters. </returns>
public static RectD TileBounds(Vector2 tileCoordinate, int zoom)
{
var min = PixelsToMeters(new Vector2d(tileCoordinate.x * TileSize, tileCoordinate.y * TileSize), zoom);
var max = PixelsToMeters(new Vector2d((tileCoordinate.x + 1) * TileSize, (tileCoordinate.y + 1) * TileSize), zoom);
return new RectD(min, max - min);
}
public static RectD TileBounds(UnwrappedTileId unwrappedTileId)
{
var min = PixelsToMeters(new Vector2d(unwrappedTileId.X * TileSize, unwrappedTileId.Y * TileSize), unwrappedTileId.Z);
var max = PixelsToMeters(new Vector2d((unwrappedTileId.X + 1) * TileSize, (unwrappedTileId.Y + 1) * TileSize), unwrappedTileId.Z);
return new RectD(min, max - min);
}
/// <summary>
/// Gets the xy tile ID at the requested zoom that contains the WGS84 lat/lon point.
/// See: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames.
/// </summary>
/// <param name="latitude"> The latitude. </param>
/// <param name="longitude"> The longitude. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> A <see cref="T:Mapbox.Map.UnwrappedTileId"/> xy tile ID. </returns>
public static UnwrappedTileId LatitudeLongitudeToTileId(double latitude, double longitude, int zoom)
{
var x = (int)Math.Floor((longitude + 180.0) / 360.0 * Math.Pow(2.0, zoom));
var y = (int)Math.Floor((1.0 - Math.Log(Math.Tan(latitude * Math.PI / 180.0)
+ 1.0 / Math.Cos(latitude * Math.PI / 180.0)) / Math.PI) / 2.0 * Math.Pow(2.0, zoom));
return new UnwrappedTileId(zoom, x, y);
}
/// <summary>
/// Get coordinates for a given latitude/longitude in tile-space. Useful when comparing feature geometry to lat/lon coordinates.
/// </summary>
/// <returns>The longitude to tile position.</returns>
/// <param name="coordinate">Coordinate.</param>
/// <param name="tileZoom">The zoom level of the tile.</param>
/// <param name="layerExtent">Layer extent. Optional, but recommended. Defaults to 4096, the standard for Mapbox Tiles</param>
public static Vector2 LatitudeLongitudeToVectorTilePosition(Vector2d coordinate, int tileZoom, ulong layerExtent = 4096)
{
var coordinateTileId = Conversions.LatitudeLongitudeToTileId(
coordinate.x, coordinate.y, tileZoom);
var _meters = LatLonToMeters(coordinate);
var _rect = Conversions.TileBounds(coordinateTileId);
//vectortile space point (0 - layerExtent)
var vectorTilePoint = new Vector2((float)((_meters - _rect.Min).x / _rect.Size.x) * layerExtent,
(float)(layerExtent - ((_meters - _rect.Max).y / _rect.Size.y) * layerExtent));
return vectorTilePoint;
}
public static Vector2 LatitudeLongitudeToUnityTilePosition(Vector2d coordinate, UnityTile tile, ulong layerExtent = 4096)
{
return LatitudeLongitudeToUnityTilePosition(coordinate, tile.CurrentZoom, tile.TileScale, layerExtent);
}
/// <summary>
/// Get coordinates for a given latitude/longitude in tile-space. Useful when comparing feature geometry to lat/lon coordinates.
/// </summary>
/// <returns>The longitude to tile position.</returns>
/// <param name="coordinate">Coordinate.</param>
/// <param name="tileZoom">The zoom level of the tile.</param>
/// <param name="tileScale">Tile scale. Optional, but recommended. Defaults to a scale of 1.</param>
/// <param name="layerExtent">Layer extent. Optional, but recommended. Defaults to 4096, the standard for Mapbox Tiles</param>
public static Vector2 LatitudeLongitudeToUnityTilePosition(Vector2d coordinate, int tileZoom, float tileScale, ulong layerExtent = 4096)
{
var coordinateTileId = Conversions.LatitudeLongitudeToTileId(
coordinate.x, coordinate.y, tileZoom);
var _rect = Conversions.TileBounds(coordinateTileId);
//vectortile space point (0 - layerExtent)
var vectorTilePoint = LatitudeLongitudeToVectorTilePosition(coordinate, tileZoom, layerExtent);
//UnityTile space
var unityTilePoint = new Vector2((float)(vectorTilePoint.x / layerExtent * _rect.Size.x - (_rect.Size.x / 2)) * tileScale,
(float)((layerExtent - vectorTilePoint.y) / layerExtent * _rect.Size.y - (_rect.Size.y / 2)) * tileScale);
return unityTilePoint;
}
/// <summary>
/// Gets the WGS84 longitude of the northwest corner from a tile's X position and zoom level.
/// See: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames.
/// </summary>
/// <param name="x"> Tile X position. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> NW Longitude. </returns>
public static double TileXToNWLongitude(int x, int zoom)
{
var n = Math.Pow(2.0, zoom);
var lon_deg = x / n * 360.0 - 180.0;
return lon_deg;
}
/// <summary>
/// Gets the WGS84 latitude of the northwest corner from a tile's Y position and zoom level.
/// See: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames.
/// </summary>
/// <param name="y"> Tile Y position. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> NW Latitude. </returns>
public static double TileYToNWLatitude(int y, int zoom)
{
var n = Math.Pow(2.0, zoom);
var lat_rad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * y / n)));
var lat_deg = lat_rad * 180.0 / Math.PI;
return lat_deg;
}
/// <summary>
/// Gets the <see cref="T:Mapbox.Utils.Vector2dBounds"/> of a tile.
/// </summary>
/// <param name="x"> Tile X position. </param>
/// <param name="y"> Tile Y position. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> The <see cref="T:Mapbox.Utils.Vector2dBounds"/> of the tile. </returns>
public static Vector2dBounds TileIdToBounds(int x, int y, int zoom)
{
var sw = new Vector2d(TileYToNWLatitude(y, zoom), TileXToNWLongitude(x + 1, zoom));
var ne = new Vector2d(TileYToNWLatitude(y + 1, zoom), TileXToNWLongitude(x, zoom));
return new Vector2dBounds(sw, ne);
}
/// <summary>
/// Gets the WGS84 lat/lon of the center of a tile.
/// </summary>
/// <param name="x"> Tile X position. </param>
/// <param name="y"> Tile Y position. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns>A <see cref="T:UnityEngine.Vector2d"/> of lat/lon coordinates.</returns>
public static Vector2d TileIdToCenterLatitudeLongitude(int x, int y, int zoom)
{
var bb = TileIdToBounds(x, y, zoom);
var center = bb.Center;
return new Vector2d(center.x, center.y);
}
/// <summary>
/// Gets the Web Mercator x/y of the center of a tile.
/// </summary>
/// <param name="x"> Tile X position. </param>
/// <param name="y"> Tile Y position. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns>A <see cref="T:UnityEngine.Vector2d"/> of lat/lon coordinates.</returns>
public static Vector2d TileIdToCenterWebMercator(int x, int y, int zoom)
{
double tileCnt = Math.Pow(2, zoom);
double centerX = x + 0.5;
double centerY = y + 0.5;
centerX = ((centerX / tileCnt * 2) - 1) * Constants.WebMercMax;
centerY = (1 - (centerY / tileCnt * 2)) * Constants.WebMercMax;
return new Vector2d(centerX, centerY);
}
/// <summary>
/// Gets the meters per pixels at given latitude and zoom level for a 256x256 tile.
/// See: http://wiki.openstreetmap.org/wiki/Zoom_levels.
/// </summary>
/// <param name="latitude"> The latitude. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> Meters per pixel. </returns>
public static float GetTileScaleInMeters(float latitude, int zoom)
{
return (float)(40075016.685578d * Math.Cos(Mathf.Deg2Rad * latitude) / Math.Pow(2f, zoom + 8));
}
/// <summary>
/// Gets the degrees per tile at given zoom level for Web Mercator tile.
/// See: http://wiki.openstreetmap.org/wiki/Zoom_levels.
/// </summary>
/// <param name="latitude"> The latitude. </param>
/// <param name="zoom"> Zoom level. </param>
/// <returns> Degrees per tile. </returns>
public static float GetTileScaleInDegrees(float latitude, int zoom)
{
return (float)(360.0f / Math.Pow(2f, zoom + 8));
}
/// <summary>
/// Gets height from terrain-rgb adjusted for a given scale.
/// </summary>
/// <param name="color"> The <see cref="T:UnityEngine.Color"/>. </param>
/// <param name="relativeScale"> Relative scale. </param>
/// <returns> Adjusted height in meters. </returns>
public static float GetRelativeHeightFromColor(Color color, float relativeScale)
{
return GetAbsoluteHeightFromColor(color) * relativeScale;
}
/// <summary>
/// Specific formula for mapbox.terrain-rgb to decode height values from pixel values.
/// See: https://www.mapbox.com/blog/terrain-rgb/.
/// </summary>
/// <param name="color"> The <see cref="T:UnityEngine.Color"/>. </param>
/// <returns> Height in meters. </returns>
public static float GetAbsoluteHeightFromColor(Color color)
{
return (float)(-10000 + ((color.r * 255 * 256 * 256 + color.g * 255 * 256 + color.b * 255) * 0.1));
}
public static float GetAbsoluteHeightFromColor32(Color32 color)
{
return (float)(-10000 + ((color.r * 256 * 256 + color.g * 256 + color.b) * 0.1));
}
public static float GetAbsoluteHeightFromColor(float r, float g, float b)
{
return (-10000f + ((r * 65536f + g * 256f + b) * 0.1f));
}
private static double Resolution(int zoom)
{
return InitialResolution / Math.Pow(2, zoom);
}
private static Vector2d PixelsToMeters(Vector2d p, int zoom)
{
var res = Resolution(zoom);
var met = new Vector2d();
met.x = (p.x * res - OriginShift);
met.y = -(p.y * res - OriginShift);
return met;
}
private static Vector2d MetersToPixels(Vector2d m, int zoom)
{
var res = Resolution(zoom);
var pix = new Vector2d(((m.x + OriginShift) / res), ((-m.y + OriginShift) / res));
return pix;
}
private static Vector2 PixelsToTile(Vector2d p)
{
var t = new Vector2((int)Math.Ceiling(p.x / (double)TileSize) - 1, (int)Math.Ceiling(p.y / (double)TileSize) - 1);
return t;
}
public static double GeoDistance(
double lon1,
double lat1,
double lon2,
double lat2)
{
var toRad = 3.141592653589793 / 180;
double dlon = (lon2 - lon1) * toRad;
double dlat = (lat2 - lat1) * toRad;
double a = (Math.Sin(dlat / 2) * Math.Sin(dlat / 2)) + Math.Cos(lat1 * toRad) * Math.Cos(lat2 * toRad) * (Math.Sin(dlon / 2) * Math.Sin(dlon / 2));
double angle = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
return angle * 6378.16;
}
}
}