Leaflet实现极地坐标投影
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Leaflet Polar Graticule - Arctic</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.2/dist/leaflet.css"/>
<style type="text/css">
html,
body,
#map {
width: 100%;
height: 100%;
margin: 0;
background: #fff;
}
</style>
</head>
<body>
<div id='map'>
<script src="https://unpkg.com/leaflet@1.9.2/dist/leaflet.js"></script>
<script src="https://unpkg.com/proj4leaflet@1.0.2/lib/proj4-compressed.js"></script>
<script src="https://unpkg.com/proj4leaflet@1.0.2/src/proj4leaflet.js"></script>
<script type="text/javascript" src="../L.Graticule.min.js"></script>
<script type="text/javascript">
const crs = new L.Proj.CRS('EPSG:3413', '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs', {
resolutions: [16184, 8192, 4096, 2048, 1024, 512, 256],
origin: [-4194304, 4194304],
bounds: L.bounds(
[-4194304, -4194304],
[4194304, 4194304]
)
});
var map = L.map('map', {
crs: crs,
maxZoom: 5,
center: [90, 0],
zoom: 0,
attributionControl: false
});
// coastlines
L.tileLayer.wms('http://10.31.14.83:9090/geoserver/polar/wms', {
layers: 'polar:coastN10',
format: 'image/png',
transparent: true,
height: 256,
width: 256
}).addTo(map);
L.graticule({
intervalLat: 10,
intervalLng: 30,
latBounds: [45, 90],
// centerLonLabels: true
}).addTo(map);
</script>
</div>
</body>
</html>
https://github.com/anton-seaice/Leaflet.PolarGraticule
点击查看L.Graticule.js代码
(function () {
'use strict';
/*
* Leaflet.TextPath - Shows text along a polyline
* Inspired by Tom Mac Wright article :
* http://mapbox.com/osmdev/2012/11/20/getting-serious-about-svg/
*/
(function () {
var __onAdd = L.Polyline.prototype.onAdd,
__onRemove = L.Polyline.prototype.onRemove,
__updatePath = L.Polyline.prototype._updatePath,
__bringToFront = L.Polyline.prototype.bringToFront;
var PolylineTextPath = {
onAdd: function (map) {
__onAdd.call(this, map);
this._textRedraw();
},
onRemove: function (map) {
map = map || this._map;
if (map && this._textNode && map._renderer._container)
map._renderer._container.removeChild(this._textNode);
__onRemove.call(this, map);
},
bringToFront: function () {
__bringToFront.call(this);
this._textRedraw();
},
_updatePath: function () {
__updatePath.call(this);
this._textRedraw();
},
_textRedraw: function () {
var text = this._text,
options = this._textOptions;
if (text) {
this.setText(null).setText(text, options);
}
},
setText: function (text, options) {
this._text = text;
this._textOptions = options;
/* If not in SVG mode or Polyline not added to map yet return */
/* setText will be called by onAdd, using value stored in this._text */
if (!L.Browser.svg || typeof this._map === 'undefined') {
return this;
}
var defaults = {
repeat: false,
fillColor: 'black',
attributes: {},
below: false,
};
options = L.Util.extend(defaults, options);
/* If empty text, hide */
if (!text) {
if (this._textNode && this._textNode.parentNode) {
this._map._renderer._container.removeChild(this._textNode);
/* delete the node, so it will not be removed a 2nd time if the layer is later removed from the map */
delete this._textNode;
}
return this;
}
text = text.replace(/ /g, '\u00A0'); // Non breakable spaces
var id = 'pathdef-' + L.Util.stamp(this);
var svg = this._map._renderer._container;
this._path.setAttribute('id', id);
if (options.repeat) {
/* Compute single pattern length */
var pattern = L.SVG.create('text');
for (var attr in options.attributes)
pattern.setAttribute(attr, options.attributes[attr]);
pattern.appendChild(document.createTextNode(text));
svg.appendChild(pattern);
var alength = pattern.getComputedTextLength();
svg.removeChild(pattern);
/* Create string as long as path */
text = new Array(Math.ceil(isNaN(this._path.getTotalLength() / alength) ? 0 : this._path.getTotalLength() / alength)).join(text);
}
/* Put it along the path using textPath */
var textNode = L.SVG.create('text'),
textPath = L.SVG.create('textPath');
var dy = options.offset || this._path.getAttribute('stroke-width');
textPath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+id);
textNode.setAttribute('dy', dy);
for (var attr in options.attributes)
textNode.setAttribute(attr, options.attributes[attr]);
textPath.appendChild(document.createTextNode(text));
textNode.appendChild(textPath);
this._textNode = textNode;
if (options.below) {
svg.insertBefore(textNode, svg.firstChild);
}
else {
svg.appendChild(textNode);
}
/* Center text according to the path's bounding box */
if (options.center) {
var textLength = textNode.getComputedTextLength();
var pathLength = this._path.getTotalLength();
/* Set the position for the left side of the textNode */
textNode.setAttribute('dx', ((pathLength / 2) - (textLength / 2)));
}
/* Change label rotation (if required) */
if (options.orientation) {
var rotateAngle = 0;
switch (options.orientation) {
case 'flip':
rotateAngle = 180;
break;
case 'perpendicular':
rotateAngle = 90;
break;
default:
rotateAngle = options.orientation;
}
var rotatecenterX = (textNode.getBBox().x + textNode.getBBox().width / 2);
var rotatecenterY = (textNode.getBBox().y + textNode.getBBox().height / 2);
textNode.setAttribute('transform','rotate(' + rotateAngle + ' ' + rotatecenterX + ' ' + rotatecenterY + ')');
}
/* Initialize mouse events for the additional nodes */
if (this.options.interactive) {
if (L.Browser.svg || !L.Browser.vml) {
textPath.setAttribute('class', 'leaflet-interactive');
}
var events = ['click', 'dblclick', 'mousedown', 'mouseover',
'mouseout', 'mousemove', 'contextmenu'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.on(textNode, events[i], this.fire, this);
}
}
return this;
}
};
L.Polyline.include(PolylineTextPath);
L.LayerGroup.include({
setText: function(text, options) {
for (var layer in this._layers) {
if (typeof this._layers[layer].setText === 'function') {
this._layers[layer].setText(text, options);
}
}
return this;
}
});
})();
/*
Graticule plugin for Leaflet powered maps.
*/
L.Graticule = L.GeoJSON.extend({
options: {
style: {
color: '#333',
weight: 1
},
intervalLat: 10 ,
intervalLng: 10 ,
latBounds:[-90,90],
lngBounds:[-180,180] ,
centerLatLabels: true ,
centerLonLabels: false ,
zIndex: 99999,
onEachFeature: function (feature, layer) {
if (feature.properties.name.match('W')) {
layer.setText(
feature.properties.name,
{offset:-3, orientation:0, center: layer.options.centerLonLabels, attributes:{class:'grat-labels'}}
);
} else if (feature.properties.name.match('E')) {
layer.setText(
feature.properties.name,
{offset:-3, orientation:180, center: layer.options.centerLonLabels, attributes:{class:'grat-labels'}}
);
} else if (feature.properties.name.match('S')) {
layer.setText(
feature.properties.name,
{offset:-3, center: layer.options.centerLatLabels, attributes:{class:'grat-labels'}}
);
} else if (feature.properties.name.match('N')) {
layer.setText(
feature.properties.name,
{offset:-3, center: layer.options.centerLatLabels, attributes:{class:'grat-labels'}}
);
}
}
},
initialize: function (options) {
L.Util.setOptions(this, options);
this.options.style={...this.options.style, interactive:false};
this._layers = {};
this.addData(this._getGraticule());
},
_getGraticule: function () {
var features = [];
//round the starting point to a multiple of the lng interval
let lng = this.options.intervalLng*Math.round(this.options.lngBounds[0]/this.options.intervalLng);
// Meridians
for (lng ; lng < this.options.lngBounds[1]; lng += this.options.intervalLng) {
if (lng >=0) {
features.push(this._getFeature(this._getMeridian(lng), {
"name": (lng) ? lng.toString() + "° E" : "0° E"
}));
}
else if (lng < 0) {
features.push(this._getFeature(this._getMeridian(lng), {
"name": (-lng).toString() + "° W"
}));
}
}
//round the starting point to a multiple of the lat interval
let lat = this.options.intervalLat*Math.round(this.options.latBounds[0]/this.options.intervalLat);
// Parallels
for (lat; lat < this.options.latBounds[1]; lat = lat + this.options.intervalLat) {
if (lat>=0) {
features.push(this._getFeature(this._getParallel(lat), {
"name": (lat) ? lat.toString() + "° N" : "0° N"
}));
}
else if (lat < 0) {
features.push(this._getFeature(this._getParallel(lat), {
"name": (-lat).toString() + "° S"
}));
}
}
return {
"type": "FeatureCollection",
"features": features
};
},
_getMeridian: function (lng) {
lng = this._lngFix(lng);
var coords = [];
// for convenience with labelling, we are going to draw lines towards the poles
if (this.options.latBounds[0]<-this.options.latBounds[1]) {
// lines start at max and go towards the min (e.g.. from -30 towards -90)
for (var lat = this.options.latBounds[1]; lat >= this.options.latBounds[0]; lat--) {
coords.push([lng, lat]);
}
} else {
// lines start at min and go towards max (e.g. from 0 towards 90)
for (var lat = this.options.latBounds[0]; lat <= this.options.latBounds[1]; lat++) {
coords.push([lng, lat]);
}
}
return coords;
},
_getParallel: function (lat) {
var coords = [];
for (var lng = this.options.lngBounds[0]; lng <= this.options.lngBounds[1]; lng++) {
coords.push([this._lngFix(lng), lat]);
}
return coords;
},
_getFeature: function (coords, prop) {
return {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": coords
},
"properties": prop
};
},
_lngFix: function (lng) {
if (lng >= 180) return 179.999999;
if (lng <= -180) return -179.999999;
return lng;
},
});
L.graticule = function (options) {
return new L.Graticule(options);
};
})();