|
22 | 22 | * @property {number} lon
|
23 | 23 | */
|
24 | 24 |
|
| 25 | +/** |
| 26 | + * (type) |
| 27 | + * |
| 28 | + * Object with x/y number values. |
| 29 | + * @typedef {Object} lonlat.types.point |
| 30 | + * @property {number} x |
| 31 | + * @property {number} y |
| 32 | + */ |
| 33 | + |
25 | 34 | /**
|
26 | 35 | * (exception type)
|
27 | 36 | *
|
@@ -287,6 +296,136 @@ module.exports.toLatFirstString = function toLatFirstString (input) {
|
287 | 296 | return ll.lat + ',' + ll.lon
|
288 | 297 | }
|
289 | 298 |
|
| 299 | +/** |
| 300 | + * Pixel conversions and constants taken from |
| 301 | + * https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Implementations |
| 302 | + */ |
| 303 | + |
| 304 | +/** |
| 305 | + * Pixels per tile. |
| 306 | + */ |
| 307 | +var PIXELS_PER_TILE = module.exports.PIXELS_PER_TILE = 256 |
| 308 | + |
| 309 | +// 2^z represents the tile number. Scale that by the number of pixels in each tile. |
| 310 | +function zScale (z) { |
| 311 | + return Math.pow(2, z) * PIXELS_PER_TILE |
| 312 | +} |
| 313 | + |
| 314 | +// Converts from degrees to radians |
| 315 | +function toRadians (degrees) { |
| 316 | + return degrees * Math.PI / 180 |
| 317 | +} |
| 318 | + |
| 319 | +// Converts from radians to degrees. |
| 320 | +function toDegrees (radians) { |
| 321 | + return radians * 180 / Math.PI |
| 322 | +} |
| 323 | + |
| 324 | +/** |
| 325 | + * Convert a longitude to it's pixel value given a `zoom` level. |
| 326 | + * |
| 327 | + * @param {number} longitude |
| 328 | + * @param {number} zoom |
| 329 | + * @return {number} pixel |
| 330 | + * @example |
| 331 | + * var xPixel = lonlat.longitudeToPixel(-70, 9) //= 40049.77777777778 |
| 332 | + */ |
| 333 | +function longitudeToPixel (longitude, zoom) { |
| 334 | + return (longitude + 180) / 360 * zScale(zoom) |
| 335 | +} |
| 336 | +module.exports.longitudeToPixel = longitudeToPixel |
| 337 | + |
| 338 | +/** |
| 339 | + * Convert a latitude to it's pixel value given a `zoom` level. |
| 340 | + * |
| 341 | + * @param {number} latitude |
| 342 | + * @param {number} zoom |
| 343 | + * @return {number} pixel |
| 344 | + * @example |
| 345 | + * var yPixel = lonlat.latitudeToPixel(40, 9) //= 49621.12736343896 |
| 346 | + */ |
| 347 | +function latitudeToPixel (latitude, zoom) { |
| 348 | + const latRad = toRadians(latitude) |
| 349 | + return (1 - |
| 350 | + Math.log(Math.tan(latRad) + (1 / Math.cos(latRad))) / |
| 351 | + Math.PI) / 2 * zScale(zoom) |
| 352 | +} |
| 353 | +module.exports.latitudeToPixel = latitudeToPixel |
| 354 | + |
| 355 | +/** |
| 356 | + * Maximum Latitude for valid Mercator projection conversion. |
| 357 | + */ |
| 358 | +var MAX_LAT = toDegrees(Math.atan(Math.sinh(Math.PI))) |
| 359 | + |
| 360 | +/** |
| 361 | + * Convert a coordinate to a pixel. |
| 362 | + * |
| 363 | + * @param {lonlat.types.input} input |
| 364 | + * @param {number} zoom |
| 365 | + * @return {Object} An object with `x` and `y` attributes representing pixel coordinates |
| 366 | + * @throws {lonlat.types.InvalidCoordinateException} |
| 367 | + * @throws {Error} If latitude is above or below `MAX_LAT` |
| 368 | + * @throws {Error} If `zoom` is undefined. |
| 369 | + * @example |
| 370 | + * var pixel = lonlat.toPixel({lon: -70, lat: 40}, 9) //= {x: 40049.77777777778, y:49621.12736343896} |
| 371 | + */ |
| 372 | +module.exports.toPixel = function toPixel (input, zoom) { |
| 373 | + var ll = normalize(input) |
| 374 | + if (ll.lat > MAX_LAT || ll.lat < -MAX_LAT) { |
| 375 | + throw new Error('Pixel conversion only works between ' + MAX_LAT + 'N and -' + MAX_LAT + 'S') |
| 376 | + } |
| 377 | + |
| 378 | + return { |
| 379 | + x: longitudeToPixel(ll.lon, zoom), |
| 380 | + y: latitudeToPixel(ll.lat, zoom) |
| 381 | + } |
| 382 | +} |
| 383 | + |
| 384 | +/** |
| 385 | + * Convert a pixel to it's longitude value given a zoom level. |
| 386 | + * |
| 387 | + * @param {number} x |
| 388 | + * @param {number} zoom |
| 389 | + * @return {number} longitude |
| 390 | + * @example |
| 391 | + * var lon = lonlat.pixelToLongitude(40000, 9) //= -70.13671875 |
| 392 | + */ |
| 393 | +function pixelToLongitude (x, zoom) { |
| 394 | + return x / zScale(zoom) * 360 - 180 |
| 395 | +} |
| 396 | +module.exports.pixelToLongitude = pixelToLongitude |
| 397 | + |
| 398 | +/** |
| 399 | + * Convert a pixel to it's latitude value given a zoom level. |
| 400 | + * |
| 401 | + * @param {number} y |
| 402 | + * @param {number} zoom |
| 403 | + * @return {number} latitude |
| 404 | + * @example |
| 405 | + * var lat = lonlat.pixelToLatitude(50000, 9) //= 39.1982053488948 |
| 406 | + */ |
| 407 | +function pixelToLatitude (y, zoom) { |
| 408 | + var latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / zScale(zoom)))) |
| 409 | + return toDegrees(latRad) |
| 410 | +} |
| 411 | +module.exports.pixelToLatitude = pixelToLatitude |
| 412 | + |
| 413 | +/** |
| 414 | + * From pixel. |
| 415 | + * |
| 416 | + * @param {lonlat.types.point} pixel |
| 417 | + * @param {number} zoom |
| 418 | + * @return {lonlat.types.output} |
| 419 | + * @example |
| 420 | + * var ll = lonlat.fromPixel({x: 40000, y: 50000}, 9) //= {lon: -70.13671875, lat: 39.1982053488948} |
| 421 | + */ |
| 422 | +module.exports.fromPixel = function fromPixel (pixel, zoom) { |
| 423 | + return { |
| 424 | + lon: pixelToLongitude(pixel.x, zoom), |
| 425 | + lat: pixelToLatitude(pixel.y, zoom) |
| 426 | + } |
| 427 | +} |
| 428 | + |
290 | 429 | function floatize (lonlat) {
|
291 | 430 | var lon = parseFloatWithAlternates([lonlat.lon, lonlat.lng, lonlat.longitude])
|
292 | 431 | var lat = parseFloatWithAlternates([lonlat.lat, lonlat.latitude])
|
|
0 commit comments