计算墨卡托d3缩放以匹配单张缩放级别

时间:2017-06-18 05:57:52

标签: d3.js leaflet map-projections mercator

给定Leaflet缩放级别(0..22),我如何计算geoMercator投影的D3缩放值?在zoom = 0时,整个世界适合单个图块(256x256)。在瓷砖中,世界大小为2 ^ zoom x 2 ^ zoom tiles。

1 个答案:

答案 0 :(得分:3)

与墨卡托地图的d3比例进行比较:

mercator地图使用以下公式:

var point =  [x, Math.log(Math.tan(Math.PI / 4 + y / 2))];  

d3地图中的比例因子基本上适用以下转换:

   point[0] =  point[0] * k;
   point[1] =  -point[1] * k;

d3墨卡托投影的默认地图比例是961 /2π,或961像素乘360度。

墨卡托公式的一个奇怪之处在于,“整个”世界的方形视图实际上将在北纬/南纬85.05113度处出现。 Web Mercators可以推动这一点,因为它们不是共形预测。宣传单将“整个”世界的范围推向了北/南大约89.15度左右。

因此,d3使用适当的墨卡托和传单使用网页墨卡特意味着缩放值可能无法很好地网格化。 GIS.stackexchange上的这个答案将提供有关两者之间差异的更多信息。

但是,你仍然可以调整两者(大部分时间)。

Yurik在下面的comments中指出的一种方法是使用缩放级别和切片大小来获得比例因子:

Web地图服务使用平铺网格,其中增加缩放级别使显示的平铺数量增加四倍。在缩放级别1,世界适合一个平铺,在缩放级别2,世界适合四个方形平铺。瓦片数量的通用公式是:

Tiles数量= 4 ^ zoomLevel

最重要的是,每次我们放大时,穿过地图(一行)所需的图块数量都会增加一倍。

以此为出发点,我们可以弄清楚如何匹配这两者。

d3 geoMercator使用961/(2*Math.PI)作为默认比例 - 这将赤道的360度(或2弧度)扩展到961像素。要将此设置为基于图块的图层,我们只需要知道图块大小和缩放级别。

我们需要知道赤道分布了多少像素,以便我们使用:

tileSize * Math.pow(2,zoomLevel)

这为我们提供了围绕赤道的所有瓷砖的宽度。然后我们可以将它除以2Pi并得到我们的d3比例:

projection.scale(tileSize * Math.pow(2,zoomLevel) / (2 * Math.PI))

由于d3 Mercator和web mercator之间存在差异,因此可能存在失真问题,具体取决于您所处的位置以及缩放的距离,但在大多数情况下这应该提供良好的对齐。

或者,我们可以使用传单地图的实际角来确定适当的比例:

D3提供了一种方法,允许投影将地理范围与svg范围相匹配:projection.fitExtent()。此方法采用geojson或几何。在这个答案中,我正在使用geojson。

.getBounds()将返回leaftlet地图范围,因此您可以非常轻松地创建geojson边界框:

var bbox = {
    "type": "Polygon",
    "coordinates": [
        [
            [mymap.getBounds()._northEast.lng, mymap.getBounds()._northEast.lat], 
            [mymap.getBounds()._northEast.lng, mymap.getBounds()._southWest.lat], 
            [mymap.getBounds()._southWest.lng, mymap.getBounds()._southWest.lat], 
            [mymap.getBounds()._southWest.lng, mymap.getBounds()._northEast.lat],
            [mymap.getBounds()._northEast.lng, mymap.getBounds()._northEast.lat]
        ]
    ]
}

请注意,绕组顺序在d3中实际上很重要 - 外圈是逆时针的

然后你所要做的就是设置你的投影以适应那个程度:

var projection = d3.geoMercator()
   .fitSize([600, 400], bbox);

使用稍加修改的leaflet example(更改的居中点和缩放),整个事情看起来像这样:

	var features = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -0.17565250396728513,
              51.510118018740904
            ],
            [
              -0.17586708068847653,
              51.509744084227556
            ],
            [
              -0.17584562301635742,
              51.50871574849058
            ],
            [
              -0.17359256744384766,
              51.50613812964363
            ],
            [
              -0.17204761505126953,
              51.50552375337273
            ],
            [
              -0.16966581344604492,
              51.50481587478995
            ],
            [
              -0.16599655151367188,
              51.50454874793857
            ],
            [
              -0.1624774932861328,
              51.504001132997686
            ],
            [
              -0.16058921813964844,
              51.5039744199054
            ],
            [
              -0.16033172607421875,
              51.50426826305929
            ],
            [
              -0.16013860702514648,
              51.5043884710761
            ],
            [
              -0.16016006469726562,
              51.50465559886706
            ],
            [
              -0.15996694564819336,
              51.50510971251776
            ],
            [
              -0.16282081604003906,
              51.505737450406535
            ],
            [
              -0.16466617584228516,
              51.5058710105437
            ],
            [
              -0.16835689544677734,
              51.50588436653591
            ],
            [
              -0.1705455780029297,
              51.506098061878475
            ],
            [
              -0.17273426055908203,
              51.506672363145654
            ],
            [
              -0.17282009124755857,
              51.50681927626061
            ],
            [
              -0.17468690872192383,
              51.508729103648925
            ],
            [
              -0.17511606216430664,
              51.50999782583918
            ],
            [
              -0.17526626586914062,
              51.510144728231545
            ],
            [
              -0.17565250396728513,
              51.510118018740904
            ]
          ]
        ]
      }
    }
  ]
};



	var mymap = L.map('mapid').setView([51.5, -0.171], 14);

	L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
		maxZoom: 18,
		attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
			'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
			'Imagery © <a href="http://mapbox.com">Mapbox</a>',
		id: 'mapbox.streets'
	}).addTo(mymap);
	

	var svg = d3.select('#mapid')
	  .append('svg')
	  .attr('width',600)
	  .attr('height',400);

	
	
	
	// Create a geojson bounding box:
	var bbox = {
		"type": "Polygon",
		"coordinates": [
			[
				[mymap.getBounds()._northEast.lng, mymap.getBounds()._northEast.lat], 
				[mymap.getBounds()._northEast.lng, mymap.getBounds()._southWest.lat], 
				[mymap.getBounds()._southWest.lng, mymap.getBounds()._southWest.lat], 
				[mymap.getBounds()._southWest.lng, mymap.getBounds()._northEast.lat],
			    [mymap.getBounds()._northEast.lng, mymap.getBounds()._northEast.lat]
			]
		]
    }
	
	
	var projection = d3.geoMercator()
	   .fitSize([600, 400], bbox);
	
	var path = d3.geoPath().projection(projection);
	
	svg.append("path")
	  .datum(features)
	  .attr('d',path);
	
		svg {
			z-index: 10000;
			position: relative;
		}
	
<div id="mapid" style="width: 600px; height: 400px;"></div>

	
	<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />

    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" integrity="sha512-07I2e+7D8p6he1SIM+1twR5TIrhUQn9+I6yjqD53JQjFiMf8EtC93ty0/5vJTZGF8aAocvHYNEDJajGdNx1IsQ==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js" integrity="sha512-A7vV8IFfih/D732iSSKi20u/ooOfj/AGehOKq0f4vLT1Zr2Y+RX7C+w8A1gaSasGtRUZpF/NZgzSAu4/Gc41Lg==" crossorigin=""></script>
	
	<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.js"></script>

我没有添加代码来在地图更改时更新投影,但这只是重新计算边界框并重新应用fitSize方法。