-
Notifications
You must be signed in to change notification settings - Fork 214
/
Conversions.cs
438 lines (388 loc) · 17.1 KB
/
Conversions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
//-----------------------------------------------------------------------
// <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;
}
}
}