<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<!-- 引入boostrap Css框架 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://getbootstrap.com/docs/5.3/assets/css/docs.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
<!-- 引入ArcGis API -->
<link rel="stylesheet" href="https://js.arcgis.com/4.25/esri/themes/dark/main.css" />
<script src="https://js.arcgis.com/4.25/"></script>
<title> 2029432 2029518 全球海盗事件可视化页面 </title>
<script>
require([
"esri/Map",
"esri/layers/FeatureLayer",
"esri/views/MapView",
"esri/core/promiseUtils",
"esri/widgets/Legend",
"esri/widgets/Home",
"esri/widgets/Slider",
"esri/widgets/Fullscreen",
"esri/renderers/HeatmapRenderer"
], (
Map,
FeatureLayer,
MapView,
promiseUtils,
Legend,
Home,
Slider,
Fullscreen,
HeatmapRenderer
) => {
//--------------------------------------------------------------------------
// 1 显示要素
//--------------------------------------------------------------------------
const layer = new FeatureLayer({
url: "https://services3.arcgis.com/XDzy9VWpT2sZyZqz/arcgis/rest/services/seapoint/FeatureServer"
// url: "https://services9.arcgis.com/RHVPKKiFTONKtxq3/arcgis/rest/services/ASAM_events_V1/FeatureServer"
});
//--------------------------------------------------------------------------
// 2 设置点击弹框
//--------------------------------------------------------------------------
popupTemplate = {
title: "{dateofocc}", //以时间为title
content: [
{
type: "fields",
fieldInfos: [
{
fieldName: "expression/type-of-hostility", //敌意类型
}
]
},
{
type: "fields",
fieldInfos: [
{
fieldName: "expression/type-of-victim", //受害者类型
}
]
},
{
type: "fields",
fieldInfos: [
{
fieldName: "victim_d",
label: "【 Name Of Victim Vessel 】" //受害者船只名称
}
]
},
{
type: "fields",
fieldInfos: [
{
fieldName: "descriptio",
label: "【 Details Of The Event 】" //详细描述
}
]
},
],
expressionInfos: [{ // Arcade表达式,将代号转化为类型名
name: "type-of-victim",
title: "【 Type Of victim 】",
expression: `Decode( $feature.victim_l,
0, 'Anchored Ship',
1,'Barge',
2, 'Cargo Ship',
3, "Fishing Vessel",
4, "Merchant Vessel",
5,"Offshore Vessel",
6,"Passenger Ship",
7,"Sailing Vessel",
8,"Tanker",
9,"Tugboat",
10,"Vessel",
11,"Unknown",
'Other'
)`
},
{ name: "type-of-hostility",
title: "【 Type Of Hostility 】",
expression: `Decode( $feature.hostilityt,
0, "Attempted Boarding",
1,"Hijacking",
2, "Kidnapping",
3, "Naval Engagement",
4, "Other",
5,"Pirate Assault",
6,"Suspicious Approach",
7,"Unknown",
'Other'
)`
}
]}
layer.popupTemplate = popupTemplate;
//--------------------------------------------------------------------------
// 3 创建 Map&MapView
//--------------------------------------------------------------------------
const map = new Map({
basemap: {
portalItem: {
id: "4f2e99ba65e34bb8af49733d9778fb8e"
}
},
layers: [layer]
});
const view = new MapView({
map: map,
container: "viewDiv",
center: [25, 20],
zoom: 2,
constraints:{
snapToZoom: false,
minScale: 120000000
},
resizeAlign: "top-left",
});
//--------------------------------------------------------------------------
// 4 UI设置
//--------------------------------------------------------------------------
// 定义UI
const applicationDiv = document.getElementById("applicationDiv");
const sliderValue = document.getElementById("sliderValue");
const playButton = document.getElementById("playButton");
const playButton2 = document.getElementById("openHeat");
const titleDiv = document.getElementById("titleDiv");
let animation = null; //初始化动画变量
// 定义进度条
const slider = new Slider({
container: "slider",
min: 1975,
max: 2010,
values: [1975],
step: 1,
visibleElements: {
rangeLabels: true
}
});
var currentYear = 1975;
// 拖动滑块时停止动画并设置展示事件为滑块时间
function inputHandler(event) {
stopAnimation();
setYear(event.value);
}
slider.on("thumb-drag", inputHandler);
// 点击播放按钮时切换动画
playButton.addEventListener("click", () => {
if (playButton.classList.contains("toggled")) {
stopAnimation();
} else {
startAnimation();
}
});
playButton2.addEventListener("click", () => {
if (playButton.classList.contains("toggled")) {
stopAnimation();
}
});
// 控件位置
view.ui.empty("top-left");
view.ui.add(titleDiv, "top-left");
view.ui.add( //返回初始位置
new Home({
view: view
}),
"top-left"
);
view.ui.add( //图例
new Legend({
view: view
}),
"bottom-left"
);
//
view.ui.add( //全屏
new Fullscreen({
view: view,
element: applicationDiv
}),
"top-right"
);
// 悬停交互
view.whenLayerView(layer).then(setupHoverTooltip);
// 以1978为动画初始时间
setYear(1975);
//--------------------------------------------------------------------------
// 5 功能实现
//--------------------------------------------------------------------------
// 5.1 设置可视化的构建年份
function setYear(value) {
currentYear = value;
sliderValue.innerHTML = Math.floor(value);
slider.viewModel.setValue(0, value);
layer.renderer = createRenderer(value);
}
// 透明度变化
function createRenderer(year) {
const opacityStops = [
{
opacity: 0.99,
value: year
},
{
opacity: 0,
value: year + 1
}
];
return {
type: "simple",
symbol: {
type: "simple-marker",
color: "rgb(0, 0, 0)",
outline: null
},
visualVariables: [
{
type: "opacity",
field: "year",
stops: opacityStops,
legendOptions: {
showLegend: false
}
},
{
type: "color",
field: "year",
legendOptions: {
title: "Happen:"
},
// 颜色变化
stops: [
{
value: year,
color: "#d8e3e7",
label: "in " + Math.floor(year)
},
{
value: year - 10,
color: "#1a94bc",
label: "in " + (Math.floor(year) - 10)
},
{
value: year - 20,
color: "#132c33",
label: "in " + (Math.floor(year) - 20)
}
]
}
]
};
}
// 5.3 设置悬浮交互弹窗
function setupHoverTooltip(layerview) {
let highlight;
const tooltip = createTooltip();
const hitTest = promiseUtils.debounce((event) => {
return view.hitTest(event).then((hit) => {
const results = hit.results.filter((result) => {
return result.graphic.layer === layer;
});
if (!results.length) {
return null;
}
return {
graphic: results[0].graphic,
screenPoint: hit.screenPoint
};
});
});
view.on("pointer-move", (event) => {
return hitTest(event).then(
(hit) => {
// 鼠标移开 移除高亮
if (highlight) {
highlight.remove();
highlight = null;
}
// 鼠标选中 添加高亮,显示悬浮交互窗
if (hit) {
const graphic = hit.graphic;
const screenPoint = hit.screenPoint;
highlight = layerview.highlight(graphic);
tooltip.show(
screenPoint,
"Happen in " + graphic.getAttribute("year")
);
} else {
tooltip.hide();
}
},
() => { }
);
});
}
// 5.4 开始动画并循环
function startAnimation() {
stopAnimation();
animation = animate(slider.values[0]);
playButton.classList.add("toggled");
}
// 5.5 停止动画
function stopAnimation() {
if (!animation) {
return;
}
animation.remove();
animation = null;
playButton.classList.remove("toggled");
}
// 5.6进度条动画
function animate(startValue) {
let animating = true;
let value = startValue;
const frame = (timestamp) => {
if (!animating) {
return;
}
// 设置进度条移动间隔
value += 0.01;
if (value > 2010) {
value = 1975;
}
setYear(value);
// 以1000帧播放进度条
setTimeout(() => {
requestAnimationFrame(frame);
}, 1000 / 1200);
};
frame();
return {
remove: () => {
animating = false;
}
};
}
// 5.7 设置热力图渲染器
let hrender = new HeatmapRenderer({
colorStops: [
{ ratio: 0, color: "rgba(255, 255, 255, 1)" },
{ ratio: 0.2, color: "rgba(255, 255, 255, 1)" },
{ ratio: 0.5, color: "rgba(255, 140, 0, 1)" },
{ ratio: 0.8, color: "rgba(255, 140, 0, 1)" },
{ ratio: 1, color: "rgba(255, 0, 0, 1)" }
],
blurRadius: 8,
maxPixelIntensity: 0.01,
minPixelIntensity: 0.0,
radius: 20
});
document.getElementById('openHeat').onclick = () => {
console.log(layer);
layer.renderer = hrender;
}
document.getElementById('closeHeat').onclick = () => {
setYear(currentYear);
}
// 5.8 筛选功能
let type = 1;
var v_l = ['Anchored Ship', "Barge", "Cargo Ship", "Fishing Vessel", "Merchant Vessel", "Offshore Vessel", "Passenger Ship", "Sailing Vessel", "Tanker", "Tugboat", "Vessel", "Unknown", "Other"];
var h_l = ["Attempted Boarding", "Hijacking", "Kidnapping", "Naval Engagement", "Other", "Pirate Assault", "Suspicious Approach", "Unknown"]
var current_l = h_l;
function changeOptions(type) {
document.getElementById('aselect');
aselect.innerHTML = '';
var tl = [];
if (type == 1) {
tl = h_l;
} else {
tl = v_l;
}
for (var i = 0; i < tl.length; i++) {
var option = document.createElement('option');
option.innerHTML = tl[i];
option.value = i;
aselect.appendChild(option);
}
}
changeOptions(type);
// 切换筛选字段
document.getElementById("input1").onclick = () => {
type = 1;
document.getElementById("input2").checked = false;
current_l = h_l
doCheck(1);
}
document.getElementById("input2").onclick = () => {
type = 2;
current_l = v_l;
document.getElementById("input1").checked = false;
doCheck(2);
}
// 筛选下拉框
function doCheck(index) {
changeOptions(index);
}
document.getElementById("aselect").onchange = () => {
var selectVal = $("#aselect option:selected").val();
var where = '';
if(type == 1){
where = 'hostilityt='+selectVal;
}else if(type == 2){
where = 'victim_l='+selectVal;
}
layer.definitionExpression = where;
}
// 取消筛选
document.getElementById("cancel").onclick = ()=>{
layer.definitionExpression='';
}
});
</script>
<style>
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#applicationDiv {
position: absolute;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
#viewDiv {
width: 100%;
height: 100%;
flex: 1 1 auto;
order: 1;
}
/* 标题 */
#titleDiv {
font-weight: 400;
font-style: normal;
font-size: 1.2019rem;
padding: 10px;
}
/* 进度条 */
#sliderContainer {
flex: 0 0 80px;
order: 2;
display: flex;
flex-flow: row;
padding: 0 12px;
}
#sliderValue {
flex: 0 0 100px;
order: 1;
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
font-size: 300%;
}
#sliderInnerContainer {
flex: 1 1 auto;
order: 2;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 20px;
}
#slider {
width: 100%;
}
/* 播放按钮 */
#playButton {
flex: 0 0 100px;
order: 3;
margin: 20px 0;
}
.toggle-button {
display: flex;
}
.toggle-button.toggled .toggle-button-icon {
color: #cc1b1b;
}
.toggle-button .toggle-button-icon {
color: #1bcc1b;
}
.toggle-button> :nth-child(2) {
display: none;
}
.toggle-button.toggled> :nth-child(1) {
display: none;
}
.toggle-button.toggled> :nth-child(2) {
display: block;
}
#openHeat,
#closeHeat{
background-color:rgb(36, 36, 36);
padding: 10px;
border-color:rgb(36, 36, 36);
color:rgb(207,207,207);
font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
}
#input1,
#input2,
#cancel,#aselect{
background-color:rgb(36, 36, 36);
padding: 8px;
border-color:rgb(36, 36, 36);
color:rgb(207,207,207);
font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
}
.heatCon {
position: absolute;
right: 10px;
top: 8vh;
}
.selectCon {
position: absolute;
right: 10px;
top: 15vh;
}
</style>
</head>
<body>
<div id="applicationDiv">
<div id="viewDiv">
<div id="titleDiv" class="esri-widget">《 Global Piracy Showcase 》</div>
</div>
<!-- 进度条 -->
<div id="sliderContainer" class="esri-widget">
<span id="sliderValue"></span>
<div id="sliderInnerContainer">
<div id="slider"></div>
</div>
<div id="playButton" class="esri-widget esri-widget--button toggle-button">
<div>
<span class="toggle-button-icon esri-icon-play" aria-label="play icon"></span>
Play
</div>
<div>
<span class="toggle-button-icon esri-icon-pause" aria-label="pause icon"></span>
Pause
</div>
</div>
</div>
<!-- 热力图 -->
<div class="heatCon">
<a class="btn btn-primary" id="openHeat" href="#" role="button" >Thermodynamic chart</a>
<a class="btn btn-primary" id="closeHeat" href="#" role="button" >Close </a>
</div>
<!-- 筛选 -->
<div class="selectCon">
<br>
<a class="btn btn-primary" id="input1" href="#" role="button" >hostility</a>
<a class="btn btn-primary" id="input2" href="#" role="button" >victim</a>
<a class="btn btn-primary" id="cancel" href="#" role="button">Unfilter</a>
<div>
<br>
<select id="aselect" class="form-select" ></select></select>
</div>
</div>
</div>
</body>
</html>