ImageryLayer - 客户端图表
此示例演示如何在客户端为 ImageryLayer 生成图表。当用户在视图中单击或拖动指针时,应用将读取并处理距离指针位置一英里范围内的像素数据。“土地覆盖类型”图表会随着指针位置的更改而立即使用新数据进行更新。
此应用程序显示美国本土的国家土地覆盖数据库 (NLCD) 2001 年土地覆盖分类栅格。
工作原理
土地覆盖影像图层将使用 lerc
格式进行初始化,该格式将返回所请求影像的原始像素值。
1
2
3
4
const imageryLayer = new ImageryLayer({
url: "https://sampleserver6.geosceneonline.cn/arcgis/rest/services/NLCDLandCover2001/ImageServer",
format: "lerc"
});
由于 lerc
格式返回图像的原始像素值,因此我们可以在浏览器中访问像素值。这些原始值可以通过 ImageryLayerView 的 pixelData 进行访问,每当用户缩放或平移视图时,它们都会更新。应用程序监视 LayerView 的更新属性以获取更新的像素值。然后,每当用户单击影像图层或将指针拖到影像图层上时,这些值都可用于创建土地覆盖类型图表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
view.whenLayerView(imageryLayer).then(layerLoaded);
function layerLoaded(layerView) {
// watch for the imagery layer view's updating property
// to get the updated pixel values
layerView.watch("updating", (value) => {
if (!value) {
pixelData = layerView.pixelData;
}
});
// when the layer loads, listen to the view's drag and click
// events to update the land cover types chart to reflect an
// area within 1 mile of the pointer location.
removeChartEvents = view.on(["drag", "click"], (event) => {
if (pixelData){
event.stopPropagation();
getLandCoverPixelInfo(event).then(updateLandCoverChart);
}
});
// raster attributes table returns categorical mapping of pixel values such as class and group
const attributesData = imageryLayer.serviceRasterInfo.attributeTable.features;
// rasterAttributeFeatures will be used to add legend labels and colors for each
// land use type
for (let index in attributesData) {
if (attributesData) {
rasterAttributeFeatures[attributesData[index].attributes.Value] = {
"Blue": attributesData[index].attributes.Blue,
"ClassName": attributesData[index].attributes.ClassName,
"Green": attributesData[index].attributes.Green,
"Red": attributesData[index].attributes.Red
};
}
}
// initialize the land cover pie chart
createLandCoverChart();
}
当用户单击视图或将指针拖到视图上时,将调用 getLandCoverPixelInfo()
函数。在此函数中,我们执行一个逻辑来读取和存储距离指针位置一英里以内的像素值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// This function is called as user drags the pointer over or clicks on the view.
// Here we figure out which pixels fall within one mile of the
// pointer location and update the chart accordingly
const getLandCoverPixelInfo = promiseUtils.debounce((event) => {
const currentExtent = pixelData.extent;
const pixelBlock = pixelData.pixelBlock;
const height = pixelBlock.height;
const width = pixelBlock.width;
// map point for the pointer location.
const point = view.toMap({
x: event.x,
y: event.y
});
// pointer x, y in pixels
const reqX = Math.ceil(event.x);
const reqY = Math.ceil(event.y);
// calculate how many meters are represented by 1 pixel.
const pixelSizeX = Math.abs(currentExtent.xmax - currentExtent.xmin) / width;
// calculate how many pixels represent one mile
const bufferDim = Math.ceil(1609 / pixelSizeX);
// figure out 2 mile extent around the pointer location
const xmin = (reqX - bufferDim < 0) ? 0 : reqX - bufferDim;
const ymin = (reqY - bufferDim < 0) ? 0 : reqY - bufferDim;
const startPixel = ymin * width + xmin;
const bufferlength = bufferDim * 2;
const pixels = pixelBlock.pixels[0];
const radius2 = bufferDim * bufferDim;
let oneMilePixelValues = [];
// cover pixels within to 2 mile rectangle
if (bufferlength) {
for (let i = 0; i <= bufferlength; i++) {
for (let j = 0; j <= bufferlength; j++) {
// check if the given pixel location is in within one mile of the pointer
// add its value to pixelValue.
if ((Math.pow(i - bufferDim, 2) + Math.pow(j - bufferDim, 2)) <= radius2){
const pixelValue = pixels[Math.floor(startPixel + i * width + j)];
}
if (pixelValue !== undefined) {
oneMilePixelValues.push(pixelValue);
}
}
}
} else {
oneMilePixelValues.push(pixels[startPixel]);
}
pixelValCount = {};
// get the count of each land type returned within one mile radius
for (let i = 0; i < oneMilePixelValues.length; i++) {
pixelValCount[oneMilePixelValues[i]] = 1 + (pixelValCount[oneMilePixelValues[i]] || 0);
}
const circle = new Circle({
center: point,
radius: bufferDim * pixelSizeX
});
graphic.geometry = circle;
});
处理图表的原始像素值后,将调用 updateLandCovertChart()
并更新图表以反映新位置的土地覆盖类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// This function is called once pixel values within one mile of the pointer
// location are processed and ready for the chart update.
function updateLandCoverChart() {
...
// pixelValCount object contains land cover types and count of pixels
// that represent that type in within one mile.
for (let index in pixelValCount) {
if (index == 0) {
landCoverTypeColors.push("rgba(255,255,255,1");
landCoverTypeLabels.push("NoData");
} else {
const color = 'rgba(' + rasterAttributeFeatures[index].Red + ', '
+ rasterAttributeFeatures[index].Green + ', ' + rasterAttributeFeatures[index].Blue + ', 1)';
landCoverTypeColors.push(color);
landCoverTypeLabels.push(rasterAttributeFeatures[index].ClassName);
}
landCoverChart.data.datasets[0].data.push(pixelValCount[index]);
}
landCoverChart.data.datasets[0].backgroundColor = landCoverTypeColors;
landCoverChart.data.labels = landCoverTypeLabels;
landCoverChart.update(0);
}