D3 实现:D3 地图后面的自定义拓扑图像
D3 Implementation: custom topology image behind D3 map
我需要显示具有拓扑/地貌阴影背景的 D3 地图。需要实现所有用户功能(例如缩放和平移)
到目前为止,我已将地图分层放置在具有拓扑结构的 PNG 上。然后我对投影进行了一些修改,使 PNG 边框与地图边框对齐。然后我允许用户缩放 PNG(例如:http://bl.ocks.org/pbogden/7363519)。结果其实很好。当我平移和缩放时,地图随 PNG 一起移动,这很棒(下图):
问题是 PNG 非常重 (20MB),整个体验严重错误到无法使用的地步。结果显然使用了较低分辨率的图像,但是当用户放大时拓扑看起来很糟糕。我尝试将 PNG 转换为 JPG ...这实际上更糟!
在 D3 中实现我的目标的最佳解决方案是什么?初步想法如下:
(1) d3.geo.tile 插件 (http://bl.ocks.org/mbostock/4132797)。这里的困难是我需要从我的 PNG 图像创建我自己的图块。这是一个有前途的途径吗?我可以在上面叠加 D3 地图吗?我找不到自定义图块的示例。
(2) 我已经看到了 OpenSeaDragon 和 D3 的成功实现 (http://bl.ocks.org/zloysmiertniy/0ab009ca832e7e0518e585bfa9a7ad59)。这里的问题是我不确定是否可以实现所需的 D3 功能(缩放、平移、过渡),以便 D3 地图和底层图像同时移动。
(3) 还有其他想法或想法吗?
要将图像变成图块,您需要有地理参考图像 - 或者能够自己对图像进行地理参考。我相信您正在使用自然地球数据集来创建此图像,因此您可以使用源 tif 文件并使用它。我通常使用 tile mill 来制作我的瓷砖(有一些 python)并且它非常简单。您将无法像图块一样使用您的 png。
但是,如果您正在寻找山体阴影或某种 elevation/terrain 纹理指示,则无需在图块集上创建。使用传单示例 here,您可以找到相当多的瓷砖供应商,ESRI.WorldShadedRelieve 看起来符合要求。这是一个演示,它被拉入 d3,并在顶部绘制了一个 topojson 功能:
var pi = Math.PI,
tau = 2 * pi;
var width = 960;
height = 500;
// Initialize the projection to fit the world in a 1×1 square centered at the origin.
var projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var tile = d3.tile()
.size([width, height]);
var zoom = d3.zoom()
.scaleExtent([1 << 11, 1 << 14])
.on("zoom", zoomed);
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
var raster = svg.append("g");
var vector = svg.append("g");
// Compute the projected initial center.
var center = projection([-98.5, 39.5]);
d3.json("https://unpkg.com/world-atlas@1/world/110m.json",function(error,data) {
vector.append("path")
.datum(topojson.feature(data,data.objects.land))
.attr("stroke","black")
.attr("stroke-width",2)
.attr("fill","none")
.attr("d",path)
// Apply a zoom transform equivalent to projection.{scale,translate,center}.
svg
.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 12)
.translate(-center[0], -center[1]));
})
function zoomed() {
var transform = d3.event.transform;
var tiles = tile
.scale(transform.k)
.translate([transform.x, transform.y])
();
projection
.scale(transform.k / tau)
.translate([transform.x, transform.y]);
var image = raster
.attr("transform", stringify(tiles.scale, tiles.translate))
.selectAll("image")
.data(tiles, function(d) {
return d;
});
image.exit().remove();
// enter:
var entered = image.enter().append("image");
// update:
image = entered.merge(image)
.attr('xlink:href', function(d) {
return 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/' + d.z + '/' + d.y + '/' + d.x + '.png';
})
.attr('x', function(d) {
return d.x * 256;
})
.attr('y', function(d) {
return d.y * 256;
})
.attr("width", 256)
.attr("height", 256);
vector.selectAll("path")
.attr("transform", "translate(" + [transform.x, transform.y] + ")scale(" + transform.k + ")")
.style("stroke-width", 1 / transform.k);
}
function stringify(scale, translate) {
var k = scale / 256,
r = scale % 1 ? Number : Math.round;
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
body { margin: 0; }
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-tile@0.0.4/build/d3-tile.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
您当然可以为此使用 OpenSeadragon。您想将图像变成图块;你不需要专门的服务器......你可以使用许多独立的脚本:
http://openseadragon.github.io/examples/creating-zooming-images/
完成后,OpenSeadragon 会为您处理缩放和平移。
要叠加 SVG 使其与缩放和平移相匹配,请使用 SVG 叠加插件:
https://github.com/openseadragon/svg-overlay
它与 D3 制作的 SVG 配合得很好。
需要注意的一件事是 OpenSeadragon 没有任何特定于地理的功能,因此您将以图像像素而不是 latitude/longitude.
定位叠加层
顺便说一句,OpenSeadragon 也可以处理非平铺图像,所以如果您想在平铺图像之前对其进行测试,那没问题。您只想在制作前平铺图像,这样您就不会向用户发送 20mb。
我需要显示具有拓扑/地貌阴影背景的 D3 地图。需要实现所有用户功能(例如缩放和平移)
到目前为止,我已将地图分层放置在具有拓扑结构的 PNG 上。然后我对投影进行了一些修改,使 PNG 边框与地图边框对齐。然后我允许用户缩放 PNG(例如:http://bl.ocks.org/pbogden/7363519)。结果其实很好。当我平移和缩放时,地图随 PNG 一起移动,这很棒(下图):
问题是 PNG 非常重 (20MB),整个体验严重错误到无法使用的地步。结果显然使用了较低分辨率的图像,但是当用户放大时拓扑看起来很糟糕。我尝试将 PNG 转换为 JPG ...这实际上更糟!
在 D3 中实现我的目标的最佳解决方案是什么?初步想法如下:
(1) d3.geo.tile 插件 (http://bl.ocks.org/mbostock/4132797)。这里的困难是我需要从我的 PNG 图像创建我自己的图块。这是一个有前途的途径吗?我可以在上面叠加 D3 地图吗?我找不到自定义图块的示例。
(2) 我已经看到了 OpenSeaDragon 和 D3 的成功实现 (http://bl.ocks.org/zloysmiertniy/0ab009ca832e7e0518e585bfa9a7ad59)。这里的问题是我不确定是否可以实现所需的 D3 功能(缩放、平移、过渡),以便 D3 地图和底层图像同时移动。
(3) 还有其他想法或想法吗?
要将图像变成图块,您需要有地理参考图像 - 或者能够自己对图像进行地理参考。我相信您正在使用自然地球数据集来创建此图像,因此您可以使用源 tif 文件并使用它。我通常使用 tile mill 来制作我的瓷砖(有一些 python)并且它非常简单。您将无法像图块一样使用您的 png。
但是,如果您正在寻找山体阴影或某种 elevation/terrain 纹理指示,则无需在图块集上创建。使用传单示例 here,您可以找到相当多的瓷砖供应商,ESRI.WorldShadedRelieve 看起来符合要求。这是一个演示,它被拉入 d3,并在顶部绘制了一个 topojson 功能:
var pi = Math.PI,
tau = 2 * pi;
var width = 960;
height = 500;
// Initialize the projection to fit the world in a 1×1 square centered at the origin.
var projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var tile = d3.tile()
.size([width, height]);
var zoom = d3.zoom()
.scaleExtent([1 << 11, 1 << 14])
.on("zoom", zoomed);
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
var raster = svg.append("g");
var vector = svg.append("g");
// Compute the projected initial center.
var center = projection([-98.5, 39.5]);
d3.json("https://unpkg.com/world-atlas@1/world/110m.json",function(error,data) {
vector.append("path")
.datum(topojson.feature(data,data.objects.land))
.attr("stroke","black")
.attr("stroke-width",2)
.attr("fill","none")
.attr("d",path)
// Apply a zoom transform equivalent to projection.{scale,translate,center}.
svg
.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 12)
.translate(-center[0], -center[1]));
})
function zoomed() {
var transform = d3.event.transform;
var tiles = tile
.scale(transform.k)
.translate([transform.x, transform.y])
();
projection
.scale(transform.k / tau)
.translate([transform.x, transform.y]);
var image = raster
.attr("transform", stringify(tiles.scale, tiles.translate))
.selectAll("image")
.data(tiles, function(d) {
return d;
});
image.exit().remove();
// enter:
var entered = image.enter().append("image");
// update:
image = entered.merge(image)
.attr('xlink:href', function(d) {
return 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/' + d.z + '/' + d.y + '/' + d.x + '.png';
})
.attr('x', function(d) {
return d.x * 256;
})
.attr('y', function(d) {
return d.y * 256;
})
.attr("width", 256)
.attr("height", 256);
vector.selectAll("path")
.attr("transform", "translate(" + [transform.x, transform.y] + ")scale(" + transform.k + ")")
.style("stroke-width", 1 / transform.k);
}
function stringify(scale, translate) {
var k = scale / 256,
r = scale % 1 ? Number : Math.round;
return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
body { margin: 0; }
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-tile@0.0.4/build/d3-tile.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
您当然可以为此使用 OpenSeadragon。您想将图像变成图块;你不需要专门的服务器......你可以使用许多独立的脚本:
http://openseadragon.github.io/examples/creating-zooming-images/
完成后,OpenSeadragon 会为您处理缩放和平移。
要叠加 SVG 使其与缩放和平移相匹配,请使用 SVG 叠加插件:
https://github.com/openseadragon/svg-overlay
它与 D3 制作的 SVG 配合得很好。
需要注意的一件事是 OpenSeadragon 没有任何特定于地理的功能,因此您将以图像像素而不是 latitude/longitude.
定位叠加层顺便说一句,OpenSeadragon 也可以处理非平铺图像,所以如果您想在平铺图像之前对其进行测试,那没问题。您只想在制作前平铺图像,这样您就不会向用户发送 20mb。