Geo类主要是定义了一些基础的地理对象,例如 Latlng、LatlngBounds,以及地图投影和坐标系。
经纬度 LatLng
与 Geometry 中的 Point 类似,不过它表示的是一个经纬度坐标点,数值要符合经纬度的取值范围
import * as Util from '../core/Util';
import {Earth} from './crs/CRS.Earth';
import {toLatLngBounds} from './LatLngBounds';
/* @class LatLng
* @aka L.LatLng
*
* 使用固定的经纬度来表示地理点位(latitude and longitude)
*
* @example
*
* ```
* var latlng = L.latLng(50.5, 30.5);
* ```
* 所有接受LatLng 对象的方法都接受一个简单数组(除非特别说明),下面的代码是等效的
*
* ```
* map.panTo([50, 30]);
* map.panTo({lat: 50, lng: 30});
* map.panTo({lat: 50, lon: 30});
* map.panTo(L.latLng(50, 30));
* ```
*
* 注意: LatLng 没有继承自 Class 对象
* 因此 point 也不能扩展子类
* 也不能用 includes 扩展 point
*/
export function LatLng(lat, lng, alt) {
// 为什么不做经纬度范围判断?
if (isNaN(lat) || isNaN(lng)) {
throw new Error(`Invalid LatLng object: (${lat}, ${lng})`);
}
// @property lat: Number
// Latitude in degrees
this.lat = +lat;
// @property lng: Number
// Longitude in degrees
this.lng = +lng;
// @property alt: Number
// Altitude in meters (optional)
if (alt !== undefined) {
this.alt = +alt;
}
}
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
// 如果两个点的位置是一样的,就返回true,经纬度的精度可能很高,所以可以指定一个误差数值
equals(obj, maxMargin) {
if (!obj) { return false; }
obj = toLatLng(obj);
const margin = Math.max(
Math.abs(this.lat - obj.lat),
Math.abs(this.lng - obj.lng));
return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
},
// @method toString(): String
// Returns a string representation of the point (for debugging purposes).
toString(precision) {
return `LatLng(${Util.formatNum(this.lat, precision)}, ${Util.formatNum(this.lng, precision)})`;
},
// @method distanceTo(otherLatLng: LatLng): Number
// 使用球面余弦定律计算两点之间距离,单位是米 [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
distanceTo(other) {
return Earth.distance(this, toLatLng(other));
},
// @method wrap(): LatLng
// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
wrap() {
return Earth.wrapLatLng(this);
},
// @method toBounds(sizeInMeters: Number): LatLngBounds
// 返回一个新的经纬度四至范围 LatLngBounds对象。 每条边距离 LatLng 都是 sizeInMeters/2 米???
toBounds(sizeInMeters) {
// 将距离换算成经纬度,40075017 为地球周长
const latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
return toLatLngBounds(
[this.lat - latAccuracy, this.lng - lngAccuracy],
[this.lat + latAccuracy, this.lng + lngAccuracy]);
},
clone() {
return new LatLng(this.lat, this.lng, this.alt);
}
};
// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
// @alternative
// @factory L.latLng(coords: Array): LatLng
// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
// @alternative
// @factory L.latLng(coords: Object): LatLng
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
// You can also use `lon` in place of `lng` in the object form.
export function toLatLng(a, b, c) {
if (a instanceof LatLng) {
return a;
}
if (Array.isArray(a) && typeof a[0] !== 'object') {
if (a.length === 3) {
return new LatLng(a[0], a[1], a[2]);
}
if (a.length === 2) {
return new LatLng(a[0], a[1]);
}
return null;
}
if (a === undefined || a === null) {
return a;
}
if (typeof a === 'object' && 'lat' in a) {
return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
}
if (b === undefined) {
return null;
}
return new LatLng(a, b, c);
}
四至范围 LatLngBounds
和 Bounds 很类似,Latlang 版本的 Bounds
import {LatLng, toLatLng} from './LatLng';
/*
* @class LatLngBounds
* @aka L.LatLngBounds
*
* Represents a rectangular geographical area on a map.
*
* @example
*
* ```js
* var corner1 = L.latLng(40.712, -74.227),
* corner2 = L.latLng(40.774, -74.125),
* bounds = L.latLngBounds(corner1, corner2);
* ```
*
* All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
*
* ```js
* map.fitBounds([
* [40.712, -74.227],
* [40.774, -74.125]
* ]);
* ```
*
* Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
*
* Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
export function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
if (!corner1) { return; }
const latlngs = corner2 ? [corner1, corner2] : corner1;
for (let i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
}
LatLngBounds.prototype = {
// @method extend(latlng: LatLng): this
// Extend the bounds to contain the given point
// @alternative
// @method extend(otherBounds: LatLngBounds): this
// Extend the bounds to contain the given bounds
extend(obj) {
const sw = this._southWest,
ne = this._northEast;
let sw2, ne2;
if (obj instanceof LatLng) {
sw2 = obj;
ne2 = obj;
} else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest;
ne2 = obj._northEast;
if (!sw2 || !ne2) { return this; }
} else {
return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
}
if (!sw && !ne) {
this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new LatLng(ne2.lat, ne2.lng);
} else {
sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng);
ne.lat = Math.max(ne2.lat, ne.lat);
ne.lng = Math.max(ne2.lng, ne.lng);
}
return this;
},
// @method pad(bufferRatio: Number): LatLngBounds
// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
// Negative values will retract the bounds.
pad(bufferRatio) {
const sw = this._southWest,
ne = this._northEast,
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new LatLngBounds(
new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
// @method getCenter(): LatLng
// Returns the center point of the bounds.
getCenter() {
return new LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
// @method getSouthWest(): LatLng
// Returns the south-west point of the bounds.
getSouthWest() {
return this._southWest;
},
// @method getNorthEast(): LatLng
// Returns the north-east point of the bounds.
getNorthEast() {
return this._northEast;
},
// @method getNorthWest(): LatLng
// Returns the north-west point of the bounds.
getNorthWest() {
return new LatLng(this.getNorth(), this.getWest());
},
// @method getSouthEast(): LatLng
// Returns the south-east point of the bounds.
getSouthEast() {
return new LatLng(this.getSouth(), this.getEast());
},
// @method getWest(): Number
// Returns the west longitude of the bounds
getWest() {
return this._southWest.lng;
},
// @method getSouth(): Number
// Returns the south latitude of the bounds
getSouth() {
return this._southWest.lat;
},
// @method getEast(): Number
// Returns the east longitude of the bounds
getEast() {
return this._northEast.lng;
},
// @method getNorth(): Number
// Returns the north latitude of the bounds
getNorth() {
return this._northEast.lat;
},
// @method contains(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point.
contains(obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
obj = toLatLng(obj);
} else {
obj = toLatLngBounds(obj);
}
const sw = this._southWest,
ne = this._northEast;
let sw2, ne2;
if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
},
// @method intersects(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
intersects(bounds) {
bounds = toLatLngBounds(bounds);
const sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
return latIntersects && lngIntersects;
},
// @method overlaps(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
overlaps(bounds) {
bounds = toLatLngBounds(bounds);
const sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
return latOverlaps && lngOverlaps;
},
// @method toBBoxString(): String
// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
toBBoxString() {
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
},
// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
equals(bounds, maxMargin) {
if (!bounds) { return false; }
bounds = toLatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
this._northEast.equals(bounds.getNorthEast(), maxMargin);
},
// @method isValid(): Boolean
// Returns `true` if the bounds are properly initialized.
isValid() {
return !!(this._southWest && this._northEast);
}
};
// TODO International date line?
// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
// @alternative
// @factory L.latLngBounds(latlngs: LatLng[])
// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
export function toLatLngBounds(a, b) {
if (a instanceof LatLngBounds) {
return a;
}
return new LatLngBounds(a, b);
}
坐标系投影 projection
投影主要是用来将球面的经纬度转换为平面坐标,为在屏幕或纸张上显示做准备。leaflet为了将经纬度地理数据显示在屏幕上,首先经过了投影转换,然后根据当前缩放级别和投影参数对投影坐标进行仿射变换,进一步转换为屏幕坐标,然后在屏幕上绘制。
/*
* @class Projection
* An object with methods for projecting geographical coordinates of the world onto
* a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
* 一个对象,它的方法可以将代表真实世界中位置的经纬度坐标投影到平面上(或者逆向换算)
* @property bounds: Bounds
* The bounds (specified in CRS units) where the projection is valid
* 投影计算适用的范围(在CRS里指定了单位)
* @method project(latlng: LatLng): Point
* Projects geographical coordinates into a 2D point.
* 将地理坐标投影到 2D 平面上
* Only accepts actual `L.LatLng` instances, not arrays.
* @method unproject(point: Point): LatLng
* The inverse of `project`. Projects a 2D point into a geographical location.
* Only accepts actual `L.Point` instances, not arrays.
* Note that the projection instances do not inherit from Leaflet's `Class` object,
* and can't be instantiated. Also, new classes can't inherit from them,
* and methods can't be added to them with the `include` function.
*/
export {LonLat} from './Projection.LonLat';
export {Mercator} from './Projection.Mercator';
export {SphericalMercator} from './Projection.SphericalMercator';
等矩形圆柱投影,或简易圆柱投影投影 — 最简单的一个投影
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @section
* Leaflet comes with a set of already defined Projections out of the box:
* Leaflet 内置了一些已经定义好的常用投影
* @projection L.Projection.LonLat
*
* 等矩形圆柱投影,或简易圆柱投影投影 — 最简单的一个投影,
* 普通GIS爱好者使用的最多,直接把xy和经纬度进行了换算。
* 同样适用于平面世界, e.g. 游戏地图。
* EPSG:4326 和Simple CRS 使用了它。
*/
export const LonLat = {
project(latlng) {
return new Point(latlng.lng, latlng.lat);
},
unproject(point) {
return new LatLng(point.y, point.x);
},
bounds: new Bounds([-180, -90], [180, 90])
};
椭球墨卡托投影:
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @projection L.Projection.Mercator
*
* 椭球墨卡托投影 — 比球面墨卡托投影要复杂一些,更加真是的还原了椭圆形的地球
* EPSG:3395 CRS 使用了他。
*/
export const Mercator = {
R: 6378137, // 长半径
R_MINOR: 6356752.314245179, // 短半径
// y轴方向对应的范围是:南纬80°至北纬84°
bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
project(latlng) {
const d = Math.PI / 180, // 角度弧度换算
r = this.R, // 半径
tmp = this.R_MINOR / r, // 短半轴/长半轴
e = Math.sqrt(1 - tmp * tmp);
let y = latlng.lat * d; // 纬度的弧度值
const con = e * Math.sin(y);
const ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
y = -r * Math.log(Math.max(ts, 1E-10));
return new Point(latlng.lng * d * r, y);
},
unproject(point) {
const d = 180 / Math.PI,
r = this.R,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
ts = Math.exp(-point.y / r);
let phi = Math.PI / 2 - 2 * Math.atan(ts);
for (let i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
con = e * Math.sin(phi);
con = Math.pow((1 - con) / (1 + con), e / 2);
dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
phi += dphi;
}
return new LatLng(phi * d, point.x * d / r);
}
};
球面墨卡托投影:
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @projection L.Projection.SphericalMercator
*
* 球面墨卡托投影 — web在线地图最常用的一种投影方式,把地球假设为了一个标准圆球。
* 几乎所有的开源或者商业瓦片地图服务都用了这个
* EPSG:3857 CRS 用了它
*/
const earthRadius = 6378137;
export const SphericalMercator = {
R: earthRadius,
// 纬度接近两极,即90°时,y值会趋向于无穷,因此人工设定了上限【投影之后长宽是地球周长,是个正方形】
MAX_LATITUDE: 85.0511287798,
project(latlng) {
// 利用三角函数,计算球面某一点的xy位置,单位是米
const d = Math.PI / 180,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
sin = Math.sin(lat * d);
return new Point(
this.R * latlng.lng * d,
this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject(point) {
const d = 180 / Math.PI;
return new LatLng(
(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
point.x * d / this.R);
},
// bounds是一个正方形,边长是地球的周长
bounds: (function () {
const d = earthRadius * Math.PI;
return new Bounds([-d, -d], [d, d]);
})()
};
内置坐标系
Leaflet内置了一些坐标系(CRS),CRS是基类,具体的坐标系扩展了CRS。
import {CRS} from './CRS';
import {Earth} from './CRS.Earth';
import {EPSG3395} from './CRS.EPSG3395';
import {EPSG3857, EPSG900913} from './CRS.EPSG3857';
import {EPSG4326} from './CRS.EPSG4326';
import {Simple} from './CRS.Simple';
CRS.Earth = Earth;
CRS.EPSG3395 = EPSG3395;
CRS.EPSG3857 = EPSG3857;
CRS.EPSG900913 = EPSG900913;
CRS.EPSG4326 = EPSG4326;
CRS.Simple = Simple;
export {CRS};
CRS类:
import {Bounds} from '../../geometry/Bounds';
import {LatLng} from '../LatLng';
import {LatLngBounds} from '../LatLngBounds';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.Base
* 定义了坐标参考系统的对象,用于将地理点投影到像素(屏幕)坐标中(或者WMS服务中其他单位的参考系统).
* 参见[spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
*
* leaflet定义了一些常用的坐标系,如果要自定义扩展,可以参考 [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
*
* Note that the CRS instances do not inherit from Leaflet's `Class` object,
* and can't be instantiated. Also, new classes can't inherit from them,
* and methods can't be added to them with the `include` function.
*/
export const CRS = {
// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
// 将指定层级下的经纬度转换为屏幕坐标.
latLngToPoint(latlng, zoom) {
const projectedPoint = this.projection.project(latlng),
scale = this.scale(zoom);
return this.transformation._transform(projectedPoint, scale);
},
// @method pointToLatLng(point: Point, zoom: Number): LatLng
// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
// zoom into geographical coordinates.
pointToLatLng(point, zoom) {
const scale = this.scale(zoom),
untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint);
},
// @method project(latlng: LatLng): Point
// 将经纬度转换为CRS接受的坐标 (e.g. meters for EPSG:3857, for passing it to WMS services).
project(latlng) {
return this.projection.project(latlng);
},
// @method unproject(point: Point): LatLng
// Given a projected coordinate returns the corresponding LatLng.
// The inverse of `project`.
unproject(point) {
return this.projection.unproject(point);
},
// @method scale(zoom: Number): Number
// 当在特定zoom层级将投影后的坐标转换为像素坐标时候需要用的scale
// 例如:墨卡托系列的坐标系下返回 256 * 2^zoom
scale(zoom) {
return 256 * Math.pow(2, zoom);
},
// @method zoom(scale: Number): Number
// Inverse of `scale()`, returns the zoom level corresponding to a scale factor of `scale`.
zoom(scale) {
return Math.log(scale / 256) / Math.LN2;
},
// @method getProjectedBounds(zoom: Number): Bounds
// Returns the projection's bounds scaled and transformed for the provided `zoom`.
getProjectedBounds(zoom) {
if (this.infinite) { return null; }
const b = this.projection.bounds,
s = this.scale(zoom),
min = this.transformation.transform(b.min, s),
max = this.transformation.transform(b.max, s);
return new Bounds(min, max);
},
// @method distance(latlng1: LatLng, latlng2: LatLng): Number
// Returns the distance between two geographical coordinates.
// @property code: String
// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
//
// @property wrapLng: Number[]
// An array of two numbers defining whether the longitude (horizontal) coordinate
// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
//
// @property wrapLat: Number[]
// Like `wrapLng`, but for the latitude (vertical) axis.
// wrapLng: [min, max],
// wrapLat: [min, max],
// @property infinite: Boolean
// If true, the coordinate space will be unbounded (infinite in both axes)
infinite: false,
// @method wrapLatLng(latlng: LatLng): LatLng
// 返回一个 LatLng,其中 lat 和 lng 已根据地图的 CRS 的 wrapLat 和 wrapLng 属性(如果它们超出 CRS 的边界)进行包装。
// 默认情况下,这意味着经度环绕在日期变更线上,因此其值介于 -180 和 +180 度之间。
wrapLatLng(latlng) {
const lng = this.wrapLng ? Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
lat = this.wrapLat ? Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
alt = latlng.alt;
return new LatLng(lat, lng, alt);
},
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
// Returns a `LatLngBounds` with the same size as the given one, ensuring
// that its center is within the CRS's bounds.
// Only accepts actual `L.LatLngBounds` instances, not arrays.
wrapLatLngBounds(bounds) {
const center = bounds.getCenter(),
newCenter = this.wrapLatLng(center),
latShift = center.lat - newCenter.lat,
lngShift = center.lng - newCenter.lng;
if (latShift === 0 && lngShift === 0) {
return bounds;
}
const sw = bounds.getSouthWest(),
ne = bounds.getNorthEast(),
newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
return new LatLngBounds(newSw, newNe);
}
};
下面是几个内置坐标系对象的定义:
Earth,基础的地球坐标系,就是一个球体。
import {CRS} from './CRS';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.Earth
*
* Serves as the base for CRS that are global such that they cover the earth.
* 因为没有专用的code、projection和transformation,因此只能被其他坐标系调用,不能直接使用。
* distance() 返回的距离单位是米
*/
export const Earth = Util.extend({}, CRS, {
wrapLng: [-180, 180],
// Mean Earth Radius, as recommended for use by
// the International Union of Geodesy and Geophysics,
// see https://rosettacode.org/wiki/Haversine_formula
R: 6371000,
// distance between two geographical points using spherical law of cosines approximation
distance(latlng1, latlng2) {
const rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return this.R * c;
}
});
EPSG4326
import {Earth} from './CRS.Earth';
import {LonLat} from '../projection/Projection.LonLat';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.EPSG4326
*
* A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
*
* Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
* which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
* with this CRS, ensure that there are two 256x256 pixel tiles covering the
* whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
* or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
*/
export const EPSG4326 = Util.extend({}, Earth, {
code: 'EPSG:4326',
projection: LonLat,
transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
});
EPSG3857
import {Earth} from './CRS.Earth';
import {SphericalMercator} from '../projection/Projection.SphericalMercator';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.EPSG3857
*
* The most common CRS for online maps, used by almost all free and commercial
* tile providers. Uses Spherical Mercator projection. Set in by default in
* Map's `crs` option.
*/
export const EPSG3857 = Util.extend({}, Earth, {
code: 'EPSG:3857',
projection: SphericalMercator,
transformation: (function () {
const scale = 0.5 / (Math.PI * SphericalMercator.R);
return toTransformation(scale, 0.5, -scale, 0.5);
}())
});
export const EPSG900913 = Util.extend({}, EPSG3857, {
code: 'EPSG:900913'
});
Simple
import {CRS} from './CRS';
import {LonLat} from '../projection/Projection.LonLat';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.Simple
*
* A simple CRS that maps longitude and latitude into `x` and `y` directly.
* May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
* axis should still be inverted (going from bottom to top). `distance()` returns
* simple euclidean distance.
*/
export const Simple = Util.extend({}, CRS, {
projection: LonLat,
transformation: toTransformation(1, 0, -1, 0),
scale(zoom) {
return Math.pow(2, zoom);
},
zoom(scale) {
return Math.log(scale) / Math.LN2;
},
distance(latlng1, latlng2) {
const dx = latlng2.lng - latlng1.lng,
dy = latlng2.lat - latlng1.lat;
return Math.sqrt(dx * dx + dy * dy);
},
infinite: true
});