ArcGIS JS API 4.x 开发实战:SceneView 交互与信息获取
引言
ArcGIS API for JavaScript 4.x 版本为Web GIS应用开发带来了强大的3D能力,其中 SceneView
是构建三维场景的核心组件。在实际应用中,我们经常需要实现用户与三维场景的交互,例如点击场景中的要素以获取其详细信息,或者在点击位置显示弹窗。本文将深入探讨如何在 ArcGIS JS API 4.x 的 SceneView
中实现点击交互、获取元素信息以及显示信息弹窗,并解决开发过程中可能遇到的常见问题。
1. SceneView 点击获取元素信息:hitTest
方法详解
在 SceneView
中,要获取用户点击位置的元素信息,核心方法是 view.hitTest(event)
。这个方法会返回一个Promise,解析后会得到一个包含点击位置处所有图形(Graphic)和图层(Layer)信息的数组。
hitTest
工作原理
当用户在 SceneView
上点击时,hitTest
方法会执行以下操作:
- 光线投射:从点击的屏幕坐标发射一条虚拟光线进入三维场景。
- 要素识别:检测这条光线穿过的所有可见图形和图层。
- 结果返回 :返回一个
HitTestResult
对象的数组,每个对象包含一个graphic
属性(如果点击到图形)和一个layer
属性(如果点击到图层)。结果数组按照与视点的距离从近到远排序。
示例代码:点击获取图形信息
以下是一个基本的示例,展示了如何监听 SceneView
的点击事件,并使用 hitTest
获取点击位置的图形信息,然后将其输出到控制台。
javascript
require([
"esri/Map",
"esri/views/SceneView",
"esri/layers/GraphicsLayer",
"esri/Graphic",
"esri/geometry/Point",
"esri/symbols/SimpleMarkerSymbol",
"esri/geometry/SpatialReference",
"dojo/domReady!"
], function(Map, SceneView, GraphicsLayer, Graphic, Point, SimpleMarkerSymbol, SpatialReference) {
// 创建一个地图
var map = new Map({
basemap: "streets"
});
// 创建一个 SceneView
var view = new SceneView({
container: "viewDiv",
map: map,
scale: 50000000,
center: [-101.17, 21.78] // 初始中心点
});
// 创建一个 GraphicsLayer 用于显示点击的图形
var graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
// 添加一个示例图形到 GraphicsLayer
var samplePoint = new Point({
x: -100,
y: 40,
spatialReference: { wkid: 4326 }
});
var sampleSymbol = new SimpleMarkerSymbol({
color: [255, 0, 0], // 红色
size: 10,
outline: {
color: [255, 255, 255],
width: 1
}
});
var sampleGraphic = new Graphic({
geometry: samplePoint,
symbol: sampleSymbol,
attributes: { // 为图形添加属性
name: "示例点",
description: "这是一个用于测试点击的示例点"
}
});
graphicsLayer.add(sampleGraphic);
// 监听视图的点击事件
view.on("click", function(event) {
// 使用 hitTest 方法获取点击位置的图形和图层信息
view.hitTest(event).then(function(response) {
if (response.results.length > 0) {
// 获取第一个点击结果
var result = response.results[0];
var graphic = result.graphic;
var layer = result.layer;
// 输出图形和图层信息到控制台
console.log("点击到的图形:", graphic);
console.log("所属图层:", layer);
// 如果点击到图形,可以进一步处理其属性
if (graphic && graphic.attributes) {
console.log("图形属性:", graphic.attributes);
// 可以在这里添加逻辑,例如在点击位置添加一个标记
// var point = new Point({
// x: event.mapPoint.x,
// y: event.mapPoint.y,
// spatialReference: new SpatialReference({ wkid: 4326 })
// });
// var markerSymbol = new SimpleMarkerSymbol({
// color: [226, 119, 40],
// outline: {
// color: [255, 255, 255],
// width: 2
// }
// });
// var clickedGraphic = new Graphic({
// geometry: point,
// symbol: markerSymbol
// });
// graphicsLayer.add(clickedGraphic);
}
} else {
console.log("未点击到任何图形或图层。");
}
});
});
});
注意事项:
hitTest
方法返回的结果是一个数组,包含所有在点击位置处的图形和图层信息。通常我们只关心第一个结果(最上层的图形)。- 如果点击位置没有图形,
results
数组将为空。
2. SceneView 点击弹窗:PopupTemplate 的应用
在获取到图形信息后,通常的需求是在点击位置显示一个信息弹窗(Popup),展示图形的详细属性。ArcGIS JS API 提供了 PopupTemplate
类来方便地定义弹窗的内容和样式。
PopupTemplate
介绍
PopupTemplate
允许你定义弹窗的标题、内容以及如何显示图形的属性。它支持以下几种内容类型:
- 字符串 :可以直接使用
{fieldName}
占位符来引用图形的属性。 - 函数:提供一个函数来动态生成HTML内容,这在需要复杂逻辑或格式化时非常有用。
- HTML:直接嵌入HTML字符串。
示例代码:点击显示弹窗
以下是一个完整的示例代码,展示了如何在 SceneView
中点击元素后弹出一个信息窗口。
javascript
require([
"esri/Map",
"esri/views/SceneView",
"esri/layers/GraphicsLayer",
"esri/Graphic",
"esri/geometry/Point",
"esri/symbols/SimpleMarkerSymbol",
"esri/PopupTemplate",
"dojo/domReady!"
], function(Map, SceneView, GraphicsLayer, Graphic, Point, SimpleMarkerSymbol, PopupTemplate) {
// 创建一个地图
var map = new Map({
basemap: "streets"
});
// 创建一个 SceneView
var view = new SceneView({
container: "viewDiv",
map: map,
scale: 50000000,
center: [-101.17, 21.78], // 初始中心点
zoom: 5
});
// 创建一个 GraphicsLayer 并添加到地图中
var graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
// 创建一个 PopupTemplate
var popupTemplate = new PopupTemplate({
title: "点位信息",
content: "经度: {x}<br>纬度: {y}<br>名称: {name}<br>描述: {description}"
});
// 添加一个示例图形到 GraphicsLayer
var point = new Point({
x: -100,
y: 40,
spatialReference: { wkid: 4326 }
});
var markerSymbol = new SimpleMarkerSymbol({
color: [226, 119, 40],
outline: {
color: [255, 255, 255],
width: 2
}
});
var graphic = new Graphic({
geometry: point,
symbol: markerSymbol,
attributes: { // 为图形添加属性
x: point.x,
y: point.y,
name: "测试点",
description: "这是一个带有弹窗信息的测试点"
},
popupTemplate: popupTemplate // 绑定 PopupTemplate
});
graphicsLayer.add(graphic);
// 监听 SceneView 的点击事件
view.on("click", function(event) {
// 使用 hitTest 方法获取点击位置的图形信息
view.hitTest(event).then(function(response) {
if (response.results.length > 0) {
// 获取第一个点击结果
var result = response.results[0];
var graphic = result.graphic;
// 如果图形有 PopupTemplate,则显示弹窗
if (graphic && graphic.popupTemplate) {
view.popup.open({
features: [graphic], // 传入图形数组
location: event.mapPoint // 弹窗显示位置
});
} else {
console.log("点击到的图形没有绑定PopupTemplate。");
}
} else {
console.log("未点击到任何图形。");
}
});
});
});
弹窗内容自定义
如果你需要显示更复杂的内容,可以在 PopupTemplate
中使用自定义函数或 HTML 模板。例如,使用函数动态生成内容:
javascript
var popupTemplate = new PopupTemplate({
title: "点位信息",
content: function(feature) {
var point = feature.geometry;
var attributes = feature.attributes;
return `
<div>
<p><b>名称:</b> ${attributes.name}</p>
<p><b>描述:</b> ${attributes.description}</p>
<p><b>经度:</b> ${point.x.toFixed(4)}</p>
<p><b>纬度:</b> ${point.y.toFixed(4)}</p>
</div>
`;
}
});
3. view.hitTest
只获取到 id
,没有其他信息?
在开发过程中,你可能会遇到 view.hitTest
返回的结果中 graphic
对象只有 id
,而没有 attributes
或其他详细信息的情况。这通常是由于以下几个原因造成的:
原因一:图形没有设置属性数据
hitTest
方法返回的 graphic
对象的 attributes
属性,直接来源于你在创建 Graphic
实例时为其设置的 attributes
对象。如果 Graphic
没有设置 attributes
属性,那么 hitTest
返回的 graphic
自然也就没有这些信息。
解决方法 :确保在创建 Graphic
时,为其设置包含所有需要信息的 attributes
对象。
javascript
const point = new Point({ /* ... */ });
const symbol = new SimpleMarkerSymbol({ /* ... */ });
const graphic = new Graphic({
geometry: point,
symbol: symbol,
attributes: { // 确保这里有你需要的属性
objectId: 1,
name: "我的第一个点",
type: "POI",
value: 123
}
});
graphicsLayer.add(graphic);
原因二:图层类型不支持返回详细属性
某些图层类型(例如 TileLayer
或 ImageryLayer
)在 hitTest
时可能只返回图层本身的信息,而不会返回其内部要素的详细属性,因为这些图层通常是预渲染的栅格数据,不包含独立的矢量要素属性。
解决方法:
- 使用
FeatureLayer
或GraphicsLayer
:如果你需要点击要素并获取其详细属性,应将数据作为FeatureLayer
或GraphicsLayer
添加到地图中。FeatureLayer
专门用于显示和查询矢量要素,而GraphicsLayer
用于显示客户端图形。 - 进行额外查询 :如果数据源是
MapImageLayer
或WMSLayer
等,你可能需要在hitTest
之后,根据点击的地理位置和图层信息,向服务器发起额外的查询请求(例如使用QueryTask
),以获取详细的要素属性。
原因三:数据加载或渲染问题
如果图形数据尚未完全加载或渲染完成,hitTest
可能无法正确识别到图形的完整信息。
解决方法:
- 确保数据已完全加载 :对于异步加载的图层(如
FeatureLayer
),可以在图层加载完成后再进行hitTest
操作。 - 检查网络请求:使用浏览器开发者工具检查网络请求,确保所有相关的资源(如要素数据)都已成功加载。
示例:为 Graphic
添加属性
javascript
require([
"esri/Graphic",
"esri/geometry/Point",
"esri/symbols/SimpleMarkerSymbol"
], function(Graphic, Point, SimpleMarkerSymbol) {
const pointGeometry = new Point({
longitude: -118.2437,
latitude: 34.0522
});
const pointSymbol = new SimpleMarkerSymbol({
color: [255, 0, 0],
size: 10
});
// 正确设置 attributes
const myGraphic = new Graphic({
geometry: pointGeometry,
symbol: pointSymbol,
attributes: {
name: "洛杉矶",
population: 3900000,
description: "美国加利福尼亚州最大城市"
},
popupTemplate: {
title: "{name}",
content: [
{ type: "fields", fieldInfos: [
{ fieldName: "population", label: "人口" },
{ fieldName: "description", label: "描述" }
]}
]
}
});
// 将 myGraphic 添加到 GraphicsLayer 或 FeatureLayer 中
// ...
});
4. 解决 ArcGIS JS API 中的 CORS 问题
在 ArcGIS JS API 开发中,你可能会遇到与CORS相关的错误,例如 PointSymbol3D
无法加载资源,并报错 No 'Access-Control-Allow-Origin' header is present on the requested resource
。这与上文提到的CORS问题本质相同,通常是由于API尝试加载的某些资源(如字体、纹理、模型、图标等)存储在不同域名下,且该服务器未正确配置CORS头。
常见原因
- 外部资源引用:你在代码中直接引用了来自非同源服务器的图片、模型或其他资源。
- ArcGIS服务资源:某些ArcGIS服务(例如自定义符号、高程服务等)可能需要跨域访问,如果其服务器未正确配置CORS,就会出现问题。
解决方法
解决 ArcGIS JS API 中的CORS问题与通用CORS解决方案类似:
-
检查资源路径 :确保
PointSymbol3D
或其他组件使用的资源(例如图标、模型等)是从当前域名或允许跨域访问的服务器加载的。如果资源是从外部服务器加载的,检查该服务器是否支持跨域请求。 -
配置服务器的 CORS 头 :如果你有权限管理资源所在的服务器,配置
Access-Control-Allow-Origin
头是最佳实践。具体配置方法请参考本文第一部分的"策略一:修改目标服务器的CORS配置"。 -
使用代理服务器:如果无法修改资源服务器的配置,可以通过自己的服务器代理请求。例如,在你的服务器上设置一个代理接口,将跨域请求转发到目标服务器,然后将结果返回给前端。这样,浏览器只会看到同源请求,避免了 CORS 问题。具体配置方法请参考本文第一部分的"策略二:使用代理服务器"。
-
将资源本地化:如果资源是静态的(例如图标、模型文件),可以将其下载并保存到你的服务器上,然后通过本地路径引用。例如:
javascriptconst pointSymbol = new PointSymbol3D({ symbolLayers: [ new IconSymbol3DLayer({ resource: { href: "/path/to/local/icon.png" } // 引用本地资源 }) ] });
-
使用 ArcGIS 提供的资源 :如果
PointSymbol3D
使用的是 ArcGIS 提供的默认资源(例如内置图标或模型),确保你的 ArcGIS API for JavaScript 版本是最新的,并且资源路径正确。ArcGIS 官方提供的资源通常已正确配置CORS。javascriptconst pointSymbol = new PointSymbol3D({ symbolLayers: [ new IconSymbol3DLayer({ resource: { href: "https://static.arcgis.com/icons/25x25.png" } // 确保资源路径正确 }) ] });
-
禁用浏览器 CORS 检查(仅用于开发环境) :与通用CORS问题一样,在开发环境中可以临时禁用浏览器的CORS检查,但绝不能在生产环境中使用。具体方法请参考本文第一部分的"策略五:禁用浏览器CORS检查"。
总结
ArcGIS JS API 4.x 为开发者提供了强大的3D能力,但同时也带来了新的交互和数据管理挑战。通过熟练运用 hitTest
方法获取场景元素信息,结合 PopupTemplate
实现友好的信息展示,并理解和解决CORS等常见问题,开发者可以构建出功能丰富、用户体验优秀的三维Web GIS应用。在开发过程中,务必注意图形属性的完整性以及跨域资源的正确配置,以确保应用的稳定性和安全性。