如何确定一个点在投影中是否被隐藏?

7
我正在使用D3和D3.geo.projection创建一个简单的世界地球仪界面,上面有数据点,可以旋转。当我只是用圆形绘制点时,一切都很正常(即当它们在地平线后面旋转时,点被“遮蔽”)。
svg.append("g")
    .attr("class","points")
    .selectAll("text")
    .data(places.features)
  .enter()

  //for circle-point------------------------------
  .append("path")
  .attr("d", path.pointRadius(function(d) {
      if (d.properties)
        return 3+(4*d.properties.scalerank);
                    }))
    .attr("d", path)

    .attr("class", "point")
    .on("click",pointClick)
;

但是现在我想绘制符号而不是圆:

svg.append("g")
    .attr("class","points")
    .selectAll("text")
    .data(places.features)
  .enter()
    //for image-------------------------------------
    .append("image")
    .attr("xlink:href", "img/x_symbol.png")
    .attr("x", -12)
    .attr("y", -12)
    .attr("width", 24)
    .attr("height", 24)
    .attr("transform", function(d) {
        return "translate(" + projection([
          d.properties.longitude,
          d.properties.latitude
        ]) + ")"
      })

    .attr("class", "point")
    .on("click",pointClick)
;

虽然现在它能正常工作,而且符号也能正确地绘制在地球上,但即使当它们跨越到地球的背面时仍会保留。 如果我有一个方法来确定它们是否被遮挡,我可以使用可见性属性将它们隐藏起来,但我没有看到d3.geo.projection中有这样的方法。您有什么想法吗?


虽然与您的问题不直接相关,但也许会激发您去创建一个示例...我注意到您正在为圆点的d属性进行两次赋值...这只是复制/粘贴错误还是您原始代码的一部分? - FernOfTheAndes
啊,是的,@FernOfTheAndes,那似乎是一行多余的代码。我假设由于我设置了 pointRadius 属性,我仍然需要调用 .attr("d", path)。省略那行代码对点样式渲染没有影响。 我不确定是否可以将相关部分提取到 fiddle 中。它依赖于两个数据 JSON 和几个 CSS。 - Craig Soich
1
@mbostock,也许您能给我一些指导吗?我可以在d3.geo.orthographic投影中使用.png图像作为点吗? - Craig Soich
3个回答

8

计算点是否可见

如果您有投影:

const chartProjection = d3.geo.orthographic();

您可以将其转换为路径函数:
const path = d3.geo.path()
  .projection(chartProjection);

然后,您可以评估每个点的可见性。对于投影后面的值,path将返回undefined
function getVisibility(d) {
  const visible = path(
    {type: 'Point', coordinates: [d.longitude, d.latitude]});

  return visible ? 'visible' : 'hidden';
}

// Update all text elements.
svg.selectAll('text')
  .attr('visibility', getVisibility);

2
这是一个更好的答案,因为它适用于任何投影。 - zeroin
对于画布,我得到的路径字符串超出了边缘。 - antony.trupe
这是解决这个问题的可靠方法。但是,警告:如果您正在使用画布,则假定您正在使用的d3.geo.path()实例已链接到画布上下文(使用path.context(ctx)方法)。如果是这样,调用path({type 'Point,...})将始终返回未定义,因为画布绘图不像SVG绘图那样产生任何字符串。为了解决这个问题,您需要一个第二个geoPath实例--没有上下文但与第一个实例相同。 - meetamit

6

好的,我至少能够通过计算投影中心和所讨论点之间的大圆距离来模拟隐藏在地球背面的图像。如果距离大于π/2,该点将超出地平线:

        .attr("opacity", function(d) {
            var geoangle = d3.geo.distance(
                    d.geometry.coordinates,
                    [
                        -projection.rotate()[0],
                        projection.rotate()[1]
                    ]);
            if (geoangle > 1.57079632679490)
            {
                return "0";
            } else {
                return "1.0";
            }
        })

我相信,随着它们接近边缘的渐变消失、禁用点击等操作,我可以让它们更加奇妙,但现在我可以继续了...


1
我知道这是一个老问题,但我想添加一个新的解决方案。 您可以使用 projection.stream 进行过滤、解析,然后返回以像素为单位的投影 x 和 y。此方法还可用于过滤掉超出当前(缩放)范围的点。 请注意,这将过滤掉所有在当前投影下不可见的点。如果您真的想保留所有点,请添加另一个循环来比较两个数组。但在我的使用响应式属性的情况下,这个方法非常有效。
let newStream = []
let stream = youCurrentProjection.stream({
  point: function (x, y) {
    newStream.push([x, y])
  }
})

arrayOfPoints.forEach(point => {
  stream.point(point.x, point.y)
})

或者构建一个闭包来传递索引。
let streamWrapper = function (x, y, index) {
  let stream = youCurrentProjection.stream({
    point: function (x, y) {
      arrayOfPoints[index].isVisible = true
    }
  })
  stream.point(x, y)
}

arrayOfPoints.forEach((point, index) => {
  streamWrapper(point.x, point.y, index)
})

这是个不错的解决方案,但它考虑了投影区域内但在地球地平线之外的点吗?(我在问;我真的不知道答案) - Craig Soich
1
“在投影区域内,但超出地球的视野范围?”我不确定我是否理解正确。但是这种方法可以过滤掉本应该看不见的点,然而“projection(point)”仍会产生有效的像素x、y坐标(即位于地球“黑暗”一侧的点)。 - KuN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接