Alejandro Acuña
2024-10-25 bf65497ed7abf9c669eb3cc0ba219dfa4494b759
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace tileserver
{
    class Mercator
    {
        /**
        * Tile size.<br>
        */
        public static int TILE_SIZE = 256;
 
        /**
        * Maximum latitude.<br>
        */
        private static double MAX_LAT = 85.05112877980659;
 
        /**
        * Minimum latitude.<br>
        */
        private static double MIN_LAT = -85.05112877980659;
    
        /**
        * Equatorial earth radius for EPSG:3857 (Mercator).<br>
        */
        private static double EARTH_RADIUS = 6378137;
 
 
        /**
        * Returns earth radius size at zoom level.<br>
        * @param aZoomlevel Zoom level.
        * @return Earth radius size at zoom level.
        */
 
        public static double radius(int aZoomlevel)
        {
            return (TILE_SIZE * (1 << aZoomlevel)) / (2.0 * Math.PI);
        }
 
 
 
 
        /**
        * Returns the absolut number of pixels in y or x, defined as: 2^Zoomlevel * TILE_WIDTH where TILE_WIDTH is the width of a tile in pixels.<br>
        * @param aZoomlevel zoom level to request pixel data.
        * @return number of pixels.
        */
 
        public static int getMaxPixels(int aZoomlevel)
        {
            return TILE_SIZE * (1 << aZoomlevel);
        }
 
 
        public static int falseEasting(int aZoomlevel)
        {
            return getMaxPixels(aZoomlevel) / 2;
        }
 
        public static int falseNorthing(int aZoomlevel)
        {
            return (-1 * getMaxPixels(aZoomlevel) / 2);
        }
 
 
 
        /**
        * Transform pixelspace to coordinates and get the distance.
        *
        * @param x1 the first x coordinate.
        * @param y1 the first y coordinate.
        * @param x2 the second x coordinate.
        * @param y2 the second y coordinate.
        * @param zoomLevel the zoom level.
        * @return the distance.
        */
 
 
        public static double getDistance(int x1, int y1, int x2, int y2, int zoomLevel)
        {
            double la1 = YToLatitude(y1, zoomLevel);
            double lo1 = XToLongitude(x1, zoomLevel);
            double la2 = YToLatitude(y2, zoomLevel);
            double lo2 = XToLongitude(x2, zoomLevel);
 
            return getDistance(la1, lo1, la2, lo2);
        }
 
 
 
 
 
        /**
        * Gets the distance using Spherical law of cosines.
        *
        * @param la1 the Latitude in degrees.
        * @param lo1 the Longitude in degrees.
        * @param la2 the Latitude from 2nd coordinate in degrees.
        * @param lo2 the Longitude from 2nd coordinate in degrees.
        * @return the distance.
        * 
        */
 
        public static double getDistance(double la1, double lo1, double la2, double lo2)
        {
            double aStartLat = ToRadians(la1);
            double aStartLong = ToRadians(lo1);
            double aEndLat = ToRadians(la2);
            double aEndLong = ToRadians(lo2);
            double distance = Math.Acos(Math.Sin(aStartLat) * Math.Sin(aEndLat) + Math.Cos(aStartLat) * Math.Cos(aEndLat) * Math.Cos(aEndLong - aStartLong));
            return (EARTH_RADIUS * distance);
        }
 
 
 
        /**
        * Transform longitude to pixelspace
        *
        * <p>
        * Mathematical optimization<br>
        * <code>
        * x = radius(aZoomlevel) * toRadians(aLongitude) + falseEasting(aZoomLevel)<br>
        * x = getMaxPixels(aZoomlevel) / (2 * PI) * (aLongitude * PI) / 180 + getMaxPixels(aZoomlevel) / 2<br>
        * x = getMaxPixels(aZoomlevel) * aLongitude / 360 + 180 * getMaxPixels(aZoomlevel) / 360<br>
        * x = getMaxPixels(aZoomlevel) * (aLongitude + 180) / 360<br>
        * </code>
        * </p>
        *
        * @param aLongitude [-180..180]
        * @return [0..2^Zoomlevel*TILE_SIZE]
        */
    
        public static double longitudeToX(double aLongitude, int aZoomlevel) 
        {
            int mp = getMaxPixels(aZoomlevel);
            double x = (mp * (aLongitude + 180L)) / 360L;
            return Math.Min(x, mp - 1);
        }
 
    
    
 
    
        /**
        * Transform longitude to pixelspace
        *
        * <p>
        * Mathematical optimization<br>
        * <code>
        * x = radius(aZoomlevel) * toRadians(aLongitude) + falseEasting(aZoomLevel)<br>
        * x = getMaxPixels(aZoomlevel) / (2 * PI) * (aLongitude * PI) / 180 + getMaxPixels(aZoomlevel) / 2<br>
        * x = getMaxPixels(aZoomlevel) * aLongitude / 360 + 180 * getMaxPixels(aZoomlevel) / 360<br>
        * x = getMaxPixels(aZoomlevel) * (aLongitude + 180) / 360<br>
        * </code>
        * </p>
        *
        * @param aLongitude [-180..180]
        * @return [0..2^Zoomlevel]
        */
    
    
        public static int longitudeToTileX(double aLongitude, int aZoomlevel) 
        {
            return (int)(longitudeToX(aLongitude, aZoomlevel) / (double)TILE_SIZE);
        }
    
    
        /**
        * Transforms latitude to pixelspace
        * <p>
        * Mathematical optimization<br>
        * <code>
        * log(u) := log((1.0 + sin(toRadians(aLat))) / (1.0 - sin(toRadians(aLat))<br>
        *
        * y = -1 * (radius(aZoomlevel) / 2 * log(u)))) - falseNorthing(aZoomlevel))<br>
        * y = -1 * (getMaxPixel(aZoomlevel) / 2 * PI / 2 * log(u)) - -1 * getMaxPixel(aZoomLevel) / 2<br>
        * y = getMaxPixel(aZoomlevel) / (-4 * PI) * log(u)) + getMaxPixel(aZoomLevel) / 2<br>
        * y = getMaxPixel(aZoomlevel) * ((log(u) / (-4 * PI)) + 1/2)<br>
        * </code>
        * </p>
        * @param aLat [-90...90]
        * @return [0..2^Zoomlevel*TILE_SIZE]
        */
    
        public static double latitudeToY(double aLat, int aZoomlevel) 
        {
            if (aLat < MIN_LAT)
            {
                aLat = MIN_LAT;
            }
            else if (aLat > MAX_LAT)
            {
                aLat = MAX_LAT;
            }
            double sinLat = Math.Sin(ToRadians(aLat));
            double log = Math.Log((1.0 + sinLat) / (1.0 - sinLat));
            int mp = getMaxPixels(aZoomlevel);
            double y = mp * (0.5 - (log / (4.0 * Math.PI)));
            return Math.Min(y, mp - 1);
        }
 
    
        /**
        * Transforms latitude to pixelspace
        * <p>
        * Mathematical optimization<br>
        * <code>
        * log(u) := log((1.0 + sin(toRadians(aLat))) / (1.0 - sin(toRadians(aLat))<br>
        *
        * y = -1 * (radius(aZoomlevel) / 2 * log(u)))) - falseNorthing(aZoomlevel))<br>
        * y = -1 * (getMaxPixel(aZoomlevel) / 2 * PI / 2 * log(u)) - -1 * getMaxPixel(aZoomLevel) / 2<br>
        * y = getMaxPixel(aZoomlevel) / (-4 * PI) * log(u)) + getMaxPixel(aZoomLevel) / 2<br>
        * y = getMaxPixel(aZoomlevel) * ((log(u) / (-4 * PI)) + 1/2)<br>
        * </code>
        * </p>
        * @param aLat [-90...90]
        * @return [0..2^Zoomlevel]
        */
    
        public static int latitudeToTileY(double aLat, int aZoomlevel) 
        {
            return (int)(latitudeToY(aLat, aZoomlevel) / TILE_SIZE);
        }
    
    
    
        /**
        * Transforms pixel coordinate X to longitude
        *
        * <p>
        * Mathematical optimization<br>
        * <code>
        * lon = toDegree((aX - falseEasting(aZoomlevel)) / radius(aZoomlevel))<br>
        * lon = 180 / PI * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel) / (2 * PI)<br>
        * lon = 180 * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel))<br>
        * lon = 360 / getMaxPixels(aZoomlevel) * (aX - getMaxPixels(aZoomlevel) / 2)<br>
        * lon = 360 * aX / getMaxPixels(aZoomlevel) - 180<br>
        * </code>
        * </p>
        * @param aX [0..2^Zoomlevel*TILE_WIDTH]
        * @return [-180..180]
        */
    
        public static double XToLongitude(int aX, int aZoomlevel) 
        {
            return ((360d * aX) / getMaxPixels(aZoomlevel)) - 180.0;
        }
 
    
    
        /**
        * Transforms pixel coordinate Y to latitude
        *
        * @param aY [0..2^Zoomlevel*TILE_WIDTH]
        * @return [MIN_LAT..MAX_LAT] is about [-85..85]
        */
    
        public static double YToLatitude(int aY, int aZoomlevel) 
        {
            aY += falseNorthing(aZoomlevel);
            double latitude = (Math.PI / 2) - (2 * Math.Atan(Math.Exp(-1.0 * aY / radius(aZoomlevel))));
            return -1 * ToDegrees(latitude);
        }
 
    
 
    
        /**
         * Ecuaciones para los metros por cada píxel
         * La distancia representada por un píxel (S) está dada por
         * 
         * S=C*cos(y)/2^(z+8)
         * *
         * donde...
         * C es la circunferencia ecuatorial de la Tierra
         * z es el nivel de zoom
         * y es la latitud del lugar cuya escala cartográfica queremos saber.
         * 
         * Asegúrese de que su calculadora esté en modo sexagesimal para los ángulos, 
         * a menos que por algún motivo desee expresar la latitud en radianes. 
         * C debe estar expresado en cualquier unidad de su interés (millas, metros, pies, cualquiera). 
         * Puesto que la Tierra tiene en realidad forma elipsoidal, habrá un pequeño error en este cálculo. 
         * Pero es muy pequeño. (0.3% es el error máximo) 
         * 
         *  grado   m / pixel   escala  
         *  1               
         *  2               
         *  3               
         *  4               
         *  5               
         *  6               
         *  7               
         *  8   1,406   610,984 1 : 2.000.000
         *  9   0,703   305,492 1 : 1.100.000
         *  10  0,352   152,746 1 : 500.000
         *  11  0,176   76,373  1 : 250.000
         *  12  0,088   38,187  1 : 150.000
         *  13  0,044   19,093  1 : 70.000
         *  14  0,022   9,547   1 : 35.000
         *  15  0,011   4,773   1 : 15.000
         *  16  0,005   2,387   1 : 8.000
         *  17  0,003   1,193   1 : 4.000
         *  18  0,001   0,597   1 : 2.000
         * 
         */
    
        public static double distanceOnePixel(double latitude, int zoom)
        {
            double C = Math.PI * 2 * EARTH_RADIUS;
            double S = (C * Math.Cos(latitude) / Math.Pow( 2, zoom + 8));
            return S;
        }
 
 
 
        public static double distanceOnePixel(int x, int y, int zoom)
        {
            double lon1 = XToLongitude(x, zoom);
            double lat1 = YToLatitude(y, zoom);
            double lon2 = XToLongitude(x + 1, zoom);
            double lat2 = YToLatitude(y, zoom);
            double distance = getDistance(lat1, lon1, lat2, lon2);
            return distance;
        }
 
 
 
        private static double ToRadians(double degrees)
        {
            return (Math.PI / 180) * degrees;
        }
 
 
 
        private static double ToDegrees(double radians)
        {
            return (180.0 / Math.PI) * radians;
        }
 
    
 
    }
}