This commit is contained in:
80937518651731347 2026-04-03 03:20:12 +08:00
commit f0608a28b0
69 changed files with 1802 additions and 326 deletions

View File

@ -10,6 +10,8 @@ module.exports = {
es6: true
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// 完全禁用 ESLint
lintOnSave: false,
// add your custom rules here
// it is base on https://github.com/vuejs/eslint-config-vue

View File

@ -51,7 +51,7 @@ module.exports = {
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
port: 8181, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,

View File

@ -12,6 +12,12 @@
<link rel="stylesheet" href="./static/css/reset.css">
<link rel="stylesheet" href="./static/js/Cesium/Widgets/widgets.css">
<script type="text/javascript" src="./static/js/Cesium/Cesium.js"></script>
<!-- 引入 Mapbox GL JS -->
<link href="./static/css/mapbox-gl-v3.16.0.css" rel="stylesheet">
<script src="./static/js/mapbox-gl-v3.16.0.js"></script>
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&type=webgl&ak=AwIb6rcMDySo2AKQ2hVGIjmFCL4J1oJK"></script>-->
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> -->
<!-- <script type="text/javascript" src="http://maps.google.com/maps/api/js?key=AIzaSyD9kf3RTO2HwggOQ1_fbZawfiKzyBNPXeY"></script> -->

View File

@ -70,6 +70,26 @@ export function hovDiving(data) {
})
}
// 遥控潜水器潜次统计
export function rovDiving(data) {
return request({
url: '/report/platform/rov/diving.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
// 无人航行潜水器潜次统计
export function auvDiving(data) {
return request({
url: '/report/platform/auv/diving.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
// 载人潜水器参航单位统计
export function hovUnit(data) {
return request({
@ -80,6 +100,24 @@ export function hovUnit(data) {
})
}
export function rovUnit(data) {
return request({
url: '/report/platform/rov/unit.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
export function auvUnit(data) {
return request({
url: '/report/platform/auv/unit.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
// 载人潜水器参航单位统计(年度)
export function hovUnitYear(data) {
return request({
@ -90,6 +128,24 @@ export function hovUnitYear(data) {
})
}
export function rovUnitYear(data) {
return request({
url: '/report/platform/rov/unit/year.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
export function auvUnitYear(data) {
return request({
url: '/report/platform/auv/unit/year.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
// 无人航行潜水器统计
export function platformAuv(data) {
return request({
@ -100,37 +156,7 @@ export function platformAuv(data) {
})
}
/** 无人航行潜水器潜次统计 */
export function auvDiving(data) {
return request({
url: '/report/platform/auv/diving.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
/** 无人航行潜水器参航单位统计 */
export function auvUnit(data) {
return request({
url: '/report/platform/auv/unit.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
/** 无人航行潜水器参航单位统计(年度) */
export function auvUnitYear(data) {
return request({
url: '/report/platform/auv/unit/year.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
// 遥控潜水器潜水器统计
// 遥控潜水器汇总统计
export function platformRov(data) {
return request({
url: '/report/platform/rov.htm',
@ -140,36 +166,6 @@ export function platformRov(data) {
})
}
/** 遥控潜水器潜次统计 */
export function rovDiving(data) {
return request({
url: '/report/platform/rov/diving.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
/** 遥控潜水器参航单位统计 */
export function rovUnit(data) {
return request({
url: '/report/platform/rov/unit.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
/** 遥控潜水器参航单位统计(年度) */
export function rovUnitYear(data) {
return request({
url: '/report/platform/rov/unit/year.htm',
method: 'post',
type: 'application/x-www-form-urlencoded',
data
})
}
/**
* ds1 报表独立域名参航科学家/单位汇总等与全局 VUE_APP_BASE_API 解耦
* @returns {string}

View File

@ -15,6 +15,7 @@ import axios from 'axios'
import qs from 'qs'
import store from './store/index'
import { extractWMTSBoundsInfo } from '@/utils/mapbox-utils'
window.sessionStorage.setItem('webInfo', 'ASOS')
Vue.use(ElementUI, { locale: Zhlocale })
@ -31,3 +32,29 @@ new Vue({
components: { App },
template: '<App/>'
})
// 定义部分全局变量
Vue.config.maps = {};
Vue.config.firstRender = true;
Vue.config.boundsInfoList = [];
Vue.config.baseUrl = process.env.NODE_ENV === 'development' ? 'http://124.222.8.13:9801' : `${window.location.protocol}//${window.location.hostname}:${window.location.port}`
Vue.config.wwwrootBaseUrl = "_content/IDSSE.OceanExplorer.Shared";
Vue.config.sprites = ["go.png", "ship.png", "ship1.png", "ship2.png", "ship3.png", "fdz.png", "shys.png", "auv.png", "aircraft.png", "aircraft-r.png", "arrow.png", "dot.png", "dot_red.png", "default.png", "info-dark.png", "warning-dark.png", "warning-red.png", "slow_sog.png", "stop_sog.png", "海底主基站.png", "石油平台.png", "岸基站.png", "circle-red.png", "circle-green.png", "circle-blue.png", "circle-yellow.png", "circle-purple.png", "circle-black.png", "circle-white.png"];
if (Vue.config.firstRender) {
Vue.config.firstRender = !Vue.config.firstRender;
fetch(`${Vue.config.baseUrl}/geoserver/gwc/service/wmts?service=WMTS&acceptVersions=1.0.0&request=GetCapabilities`)
.then(response => response.text())
.then(async (xmlContent) => {
Vue.config.boundsInfoList = await extractWMTSBoundsInfo(xmlContent);
});
}
window.mapboxgl.accessToken = 'pk.eyJ1IjoicWRod2kiLCJhIjoiY2xiaXppcjEyMGNhMjNwbzVrcHl4d20zOSJ9.0ETMJM3-zllFh2GRrRVUZg'
Vue.config.tiles = {
"world_horizon_1": `${Vue.config.baseUrl}/geoserver/gwc/service/wmts/rest/ougp:world_horizon_1/EPSG:900913/EPSG:900913:{z}/{y}/{x}?format=image/jpeg`,
"World_Imagery": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
"GEBCO_basemap_NCEI": "https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/GEBCO_basemap_NCEI/MapServer/tile/{z}/{y}/{x}",
"gaode":"https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&scl=2&style=8&x={x}&y={y}&z={z}"
};

View File

@ -1779,4 +1779,64 @@ header{
}
}
.mapboxgl-ctrl-logo {
display: none !important;
}
.popup-table {
border-spacing: 0;
font-family: 'Microsoft YaHei';
color: #006400;
/*border-collapse: collapse;
empty-cells: show;*/
table-layout: fixed;
word-break: break-all;
word-wrap: break-word;
border-collapse: collapse;
/* min-width:300px;
max-width:400px;*/
width: auto;
}
.popup-table caption {
font: italic 85%/1 arial, sans-serif;
padding: 1em 0;
text-align: center;
}
.popup-table td,
.popup-table th {
border: 1px solid rgba(0, 100, 255, 0.2);
border-width: 1px 1px 1px 1px;
font-size: inherit;
padding: 3px;
overflow: visible;
/*white-space: normal;*/
}
.popup-table thead {
font-family: 'Microsoft YaHei';
background-color: rgba(0, 100, 255, 0.2);
text-align: center;
vertical-align: bottom;
}
.popup-table td {
background-color: transparent;
}
.popup-table-odd td {
background-color: #f2f2f2;
}
.popup-table-td-left td {
text-align: left;
}
.popup-table-td-center td {
text-align: center;
}
.popup-table-td-right td {
text-align: right;
}

View File

@ -0,0 +1,185 @@
export class ColorGenerator {
// 生成适合地图显示的区分度高的颜色
static generateDistinctColors(count) {
const colors = [];
for (let i = 0; i < count; i++) {
// 使用黄金角分布算法,确保颜色分布均匀
const hue = (i * 137.5) % 360; // 黄金角约等于 137.5 度
const saturation = 70 + Math.random() * 20; // 70-90% 饱和度
const lightness = 45 + Math.random() * 20; // 45-65% 亮度
const hsl = { h: hue, s: saturation, l: lightness };
colors.push(HSLColorGenerator.toCSS(hsl));
}
return colors;
}
// 生成分类数据颜色(确保相邻类别颜色差异明显)
static generateCategoricalColors(count, startHue = 0) {
const hueStep = 360 / count;
const colors = [];
for (let i = 0; i < count; i++) {
const hue = (startHue + i * hueStep) % 360;
const hsl = {
h: hue,
s: 80 + Math.random() * 15, // 80-95% 饱和度
l: 50 + Math.random() * 15 // 50-65% 亮度
};
colors.push(HSLColorGenerator.toCSS(hsl));
}
return colors;
}
// 生成连续数据颜色(渐变)
static generateSequentialColors(
count,
startColor = '#f7fbff',
endColor = '#08306b'
) {
const colors = [];
// 解析起始颜色和结束颜色
const startRGB = this.parseHexColor(startColor);
const endRGB = this.parseHexColor(endColor);
for (let i = 0; i < count; i++) {
const ratio = i / (count - 1);
const r = Math.round(startRGB.r + (endRGB.r - startRGB.r) * ratio);
const g = Math.round(startRGB.g + (endRGB.g - startRGB.g) * ratio);
const b = Math.round(startRGB.b + (endRGB.b - startRGB.b) * ratio);
colors.push(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`);
}
return colors;
}
// 生成发散颜色(用于有正负值的数据)
static generateDivergingColors(
count,
negativeColor = '#d73027',
neutralColor = '#ffffbf',
positiveColor = '#1a9850'
) {
const colors = [];
const midPoint = Math.floor(count / 2);
// 负值部分
const negRGB = this.parseHexColor(negativeColor);
const neutralRGB = this.parseHexColor(neutralColor);
for (let i = 0; i < midPoint; i++) {
const ratio = i / midPoint;
const r = Math.round(negRGB.r + (neutralRGB.r - negRGB.r) * ratio);
const g = Math.round(negRGB.g + (neutralRGB.g - negRGB.g) * ratio);
const b = Math.round(negRGB.b + (neutralRGB.b - negRGB.b) * ratio);
colors.push(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`);
}
// 正值部分
const positiveRGB = this.parseHexColor(positiveColor);
for (let i = midPoint; i < count; i++) {
const ratio = (i - midPoint) / (count - midPoint - 1);
const r = Math.round(neutralRGB.r + (positiveRGB.r - neutralRGB.r) * ratio);
const g = Math.round(neutralRGB.g + (positiveRGB.g - neutralRGB.g) * ratio);
const b = Math.round(neutralRGB.b + (positiveRGB.b - neutralRGB.b) * ratio);
colors.push(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`);
}
return colors;
}
// 解析十六进制颜色
static parseHexColor(hex) {
hex = hex.replace('#', '');
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
return {
r: parseInt(hex.substring(0, 2), 16),
g: parseInt(hex.substring(2, 4), 16),
b: parseInt(hex.substring(4, 6), 16)
};
}
}
export class HSLColorGenerator {
// 随机 HSL 颜色
static randomHSL() {
return {
h: Math.floor(Math.random() * 360),
s: Math.floor(Math.random() * 101),
l: Math.floor(Math.random() * 101)
};
}
// 转换为 CSS 字符串
static toCSS(color) {
if (color.alpha !== undefined) {
return `hsla(${color.h}, ${color.s}%, ${color.l}%, ${color.alpha})`;
}
return `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
}
// 生成鲜艳的颜色(高饱和度)
static randomVibrantHSL(minSaturation = 80, minLightness = 40) {
return {
h: Math.floor(Math.random() * 360),
s: minSaturation + Math.floor(Math.random() * (101 - minSaturation)),
l: minLightness + Math.floor(Math.random() * (61 - minLightness))
};
}
// 生成柔和的颜色(低饱和度)
static randomPastelHSL(maxSaturation = 40, minLightness = 70) {
return {
h: Math.floor(Math.random() * 360),
s: Math.floor(Math.random() * (maxSaturation + 1)),
l: minLightness + Math.floor(Math.random() * (101 - minLightness))
};
}
// 转换为 RGB
static toRGB(hsl) {
const h = hsl.h / 360;
const s = hsl.s / 100;
const l = hsl.l / 100;
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
alpha: hsl.alpha
};
}
}

331
src/utils/mapbox-utils.js Normal file
View File

@ -0,0 +1,331 @@
import Vue from 'vue'
import { loadVectorLayer } from '@/utils/vector-layer-utils'
export function initMapbox(mapId, options = {}) {
const map = new window.mapboxgl.Map({
container: mapId,
// style: 'mapbox://styles/mapbox/standard',
style: {
"version": 8,
"name": "default_style",
// mapbox地图使用的图标
"sprite": `${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/sprites/sprite`,
// 使用官方标注字体(需联网)
"glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
// 自部署标注字体无需联网glyphs 前有两个斜杠时可能导致404
// "glyphs": `${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/glyphs/{fontstack}/{range}.pbf`,
"sources": {
"影像地图": {
"type": "raster",
"tiles": [
Vue.config.tiles[options.tileKey || "gaode"]
],
"tileSize": 256,
// "minzoom": 0,
// "maxzoom": baseMapMaxZoom
}
},
"layers": [
{
"id": "影像地图",
"type": "raster",
"source": "影像地图",
"layout": { visibility: "visible" },
// "minzoom": 0,
// "maxzoom": baseMapMaxZoom
}
]
},
config: {
basemap: {
theme: 'monochrome',
lightPreset: 'night'
}
},
zoom: options.zoom || 5,
center: options.center || [112, 20],
minZoom: options.minZoom || 2,
maxZoom: options.maxZoom || 29,
trackResize: true,
scrollZoom: false, // 禁用滚轮缩放
// 地图默认字体
localIdeographFontFamily: "Microsoft YoHei, 'Noto Sans', 'Noto Sans CJK SC', sans-serif",
pitch: 0,
bearing: 0,
// 旋转和俯仰角控制
dragRotate: options.dragRotate || false,
touchZoomRotate: options.touchZoomRotate || false,
pitchWithRotate: options.pitchWithRotate || false,
// maxBounds: [[-180, -90], [180, 90]],// Set the map's geographical boundaries.
antialias: true,
attributionControl: false,
boxZoom: true,//如果为true则启用了“缩放框”交互按住shift并在地图上拖框放大
preserveDrawingBuffer: false,//如果为true地图的canvas可以使用导出到png通过map.getCanvas().toDataURL()。默认为false会提高地图性能。
projection: 'equirectangular', // 设置投影方式经纬度直投解决缩放级别大于26时瓦片消失的问题
});
Vue.config.maps[mapId] = map;
// 确保窗口大小改变时,地图能正确铺满容器
window.addEventListener('resize', (event) => {
map.resize();
// // maps.forEach((key: string, value: any) => {
// // console.log(`${key}: ${value}`);
// // });
// for (let [key, value] of Object.entries(maps)) {
// // value.resize();
// }
// Object.values(maps).forEach((map) => {
// map.resize();
// });
});
// handleWheel("voyageMap_ship");
Vue.config.maps[mapId].scrollZoom.enable();
initSprites(mapId)
map.on('load', () => {
// 加载海岸线
if (options.coastlineOn || false)
loadVectorLayer("GSHHS_f_L1", ["line"], ["#5c71c3"], "visible", mapId, 'ougp');
// 加载等深线
if (options.contourOn || false) {
loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "visible", mapId, 'ougp');
loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "visible", mapId, 'ougp');
}
});
}
/**
* 获取所有图层信息
* @param {mapboxgl.Map} map - Mapbox 地图实例
* @returns {Array} 图层信息数组
*/
export function getAllLayers(map) {
if (!map || !map.getStyle()) {
return [];
}
const style = map.getStyle();
return style.layers || [];
}
/**
* 安全地设置图层属性
* @param {mapboxgl.Map} map - Mapbox 地图实例
* @param {string} layerId - 图层ID
* @param {string} property - 属性名
* @param {any} value - 属性值
* @returns {boolean}
*/
export function safeSetPaintProperty(map, layerId, property, value) {
if (!map.getLayer(layerId)) {
console.warn(`图层 ${layerId} 不存在,无法设置属性`);
return false;
}
try {
map.setPaintProperty(layerId, property, value);
return true;
} catch (error) {
console.error(`设置图层 ${layerId} 属性 ${property} 失败:`, error);
return false;
}
}
export function handleWheel(mapId = "homeMap") {
document.addEventListener("keydown", () => {
// 允许鼠标滚轮缩放
Vue.config.maps[mapId].scrollZoom.enable();
}, { once: false, passive: false });
document.addEventListener("keyup", () => {
// 禁止鼠标滚轮缩放
Vue.config.maps[mapId].scrollZoom.disable();
}, { once: false, passive: false });
}
export function initSprites(mapId = "homeMap") {//../../../static/images/logo.png
for (const sprite of Vue.config.sprites) {
// Vue.config.maps[mapId].loadImage(`${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/sprites/${sprite}`, function (error, image) {
Vue.config.maps[mapId].loadImage(`./static/sprites/${sprite}`, function (error, image) {
if (error) throw error;
if (!Vue.config.maps[mapId].hasImage(sprite))
// 为图像启用 sdf { sdf: true },启用的 sdf 的图像,在 Symbol 图层中就能够通过 icon-color 属性来重新渲染图像的颜色。
Vue.config.maps[mapId].addImage(sprite, image, { sdf: false });
});
}
}
export async function extractWMTSBoundsInfo(xmlContent) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlContent, "text/xml");
const featureTypes = xmlDoc.getElementsByTagName("Layer");
const result = [];
for (let i = 0; i < featureTypes.length; i++) {
const featureType = featureTypes[i];
// 提取 Name
const nameElement = featureType.getElementsByTagName("ows:Identifier")[0];
const name = nameElement ? nameElement.textContent || "" : "";
// 提取 WGS84BoundingBox
const wgs84BoundingBox = featureType.getElementsByTagName("ows:WGS84BoundingBox")[0];
if (wgs84BoundingBox) {
const lowerCorner = wgs84BoundingBox.getElementsByTagName("ows:LowerCorner")[0].textContent;
const upperCorner = wgs84BoundingBox.getElementsByTagName("ows:UpperCorner")[0].textContent;
if (name) {
result.push({
layerName: name,
bounds: [Number(lowerCorner.split(" ")[0]), Number(lowerCorner.split(" ")[1]), Number(upperCorner.split(" ")[0]), Number(upperCorner.split(" ")[1])]
});
}
}
}
return result;
}
/**
* 触发指定图层的点击事件
* @param {mapboxgl.Map} map - Mapbox地图实例
* @param {string} layerId - 要触发的图层ID
* @param {Array} lnglat - 经纬度 [经度, 纬度]
* @param {Object} options - 附加选项
*/
export async function triggerLayerClick(mapId, sourceLayerId, layerId, lnglat, options = {}) {
if (!Vue.config.maps[mapId] || !layerId || !lnglat) {
console.error('缺少必要参数');
return null;
}
const [lng, lat] = Array.isArray(lnglat) ? lnglat : [lnglat.lng, lnglat.lat];
// 1. 确保地图和图层存在
if (!Vue.config.maps[mapId].getLayer(layerId)) {
console.error(`图层 ${layerId} 不存在`);
return null;
}
// 2. 将经纬度转换为屏幕坐标
const point = Vue.config.maps[mapId].project([lng, lat]);
// 3. 查询该点/源下的图层要素
const bbox = [
[point.x - 1, point.y - 1],
[point.x + 1, point.y + 1]
];
const map = Vue.config.maps[mapId];
console.log("layerId: ", layerId, " sourceLayerId: ", sourceLayerId, " options: ", options);
// 尝试从图层获取 source id一些图层的 source 名称与 layerId 不同)
const layerObj = map.getLayer(layerId);
const sourceId = (layerObj && layerObj.source) ? layerObj.source : layerId;
const source = map.getSource(sourceId);
// 在浏览器控制台或 triggerLayerClick 中运行(替换 sourceId/sourceLayerName
const feats = map.querySourceFeatures(sourceId, { sourceLayer: "sample_station_TS-46-1_Ship_CTDI_SY" }) || [];
console.log(sourceId, ">>>>>>>>>>>>>>>>>>>>sample_station_TS-46-1_Ship_CTDI_SY", ' ======sample of features:', feats.slice(0, 5));
console.log('props keys:', feats.slice(0, 5).map(f => Object.keys(f.properties || {})));
let relatedTracks = map.querySourceFeatures(sourceId, {
sourceLayer: sourceId,
// 支持的过滤条件:==, !=, >, >=, <, <=, in, !in, all, any, none, has, !has
filter: ['==', ['get', 'sample_name'], options.sample_name]
});
console.log("relatedTracks: ", relatedTracks);
// 打印样式中与该 source 关联的所有 source-layer便于调试 vector tiles 的 layer 名称)
try {
const styleLayers = map.getStyle().layers || [];
const sourceLayerNames = new Set();
styleLayers.forEach(l => {
if (l.source === sourceId && l['source-layer']) sourceLayerNames.add(l['source-layer']);
});
console.log('candidate source-layer names for', sourceId, Array.from(sourceLayerNames));
} catch (err) {
console.warn('failed to enumerate style layers for debugging:', err);
}
// 使用表达式 ['==', ['get', prop], value] 来做精确匹配
const propName = 'sample_name';
const propValue = options.sample_name;
const filterExpr = ['==', ['get', propName], propValue];
// const filterExpr = ['==', propName, propValue];
let features = [];
try {
if (source) {
// 如果 source 尚未加载完,等待一次 sourcedata有时 vector tiles 尚未请求完成)
if (!map.isSourceLoaded(sourceId)) {
console.log('source not loaded yet, waiting for sourcedata:', sourceId);
await new Promise((resolve) => {
const onData = (e) => {
if (e && e.sourceId === sourceId && map.isSourceLoaded(sourceId)) {
map.off('sourcedata', onData);
resolve();
}
};
// 超时保护2秒后继续
const timeout = setTimeout(() => {
map.off('sourcedata', onData);
resolve();
}, 2000);
map.on('sourcedata', onData);
});
}
console.log("sourceId: ", sourceId, " sourceLayerId: ", sourceLayerId, " options: ", options);
// 优先使用 querySourceFeatures对 vector/geojson 源性能更好)
features = map.querySourceFeatures(sourceId, { sourceLayer: sourceLayerId, filter: filterExpr }) || [];
}
} catch (err) {
console.warn('querySourceFeatures failed:', err);
}
// 如果通过 source 查询不到再退回到渲染层查询rendered features作为兜底
if (!features || features.length === 0) {
try {
features = map.queryRenderedFeatures(bbox, { layers: [layerId], filter: filterExpr }) || [];
} catch (err) {
console.warn('queryRenderedFeatures fallback failed:', err);
features = [];
}
}
console.log('resolved sourceId:', sourceId, 'found features:', features.length);
// 4. 创建事件对象
const eventObject = {
lngLat: { lng, lat },
point: point,
features: features,
options: options,
originalEvent: createSimulatedEvent(point),
target: Vue.config.maps[mapId].getCanvas(),
type: 'click',
layerId: layerId,
timestamp: Date.now()
};
// 5. 触发图层特定的点击事件
Vue.config.maps[mapId].fire('click', eventObject, { layerId: layerId });
return {
event: eventObject,
features: features,
success: true
};
}
function createSimulatedEvent(point) {
return {
type: 'click',
clientX: point.x,
clientY: point.y,
preventDefault: () => { },
stopPropagation: () => { }
};
}

View File

@ -0,0 +1,538 @@
import Vue from 'vue'
import { regInfo } from '@/api/dataSearch'
let sourceLayer = undefined;
let clickedLineIds = [];
// 单例 Popup用于确保地图上只有一个弹框实例
let sharedPopup = null;
export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "homeMap", workspace = 'dsds', textField = "name") {
// 添加数据源
const sourceId = `vector-${layerId}`;
let source = Vue.config.maps[mapId].getSource(sourceId);
if (!source) {
// request vector tiles with extra buffer to avoid geometry clipping at tile edges
const tilesUrl = `${Vue.config.baseUrl}/geoserver/gwc/service/tms/1.0.0/${workspace}:${layerId}@EPSG:900913@pbf/{z}/{x}/{y}.pbf?buffer=64`;
let bb = Vue.config.boundsInfoList.find(x => x.layerName.toLowerCase() == `${workspace}:${layerId}`.toLocaleLowerCase());
let bounds = bb ? bb.bounds : null;
if (bounds) {
Vue.config.maps[mapId].addSource(sourceId, {
type: 'vector',
scheme: 'tms',
tiles: [tilesUrl],
minzoom: 0,
maxzoom: 30,
bounds: bounds,
generateId: true
});
}
else {
Vue.config.maps[mapId].addSource(sourceId, {
type: 'vector',
scheme: 'tms',
tiles: [tilesUrl],
minzoom: 0,
maxzoom: 30,
generateId: true
});
}
}
let i = 0;
features.forEach((feature) => {
loadVectorFeature(layerId, feature, colors[i++], visibility, mapId, textField);
});
}
export async function loadVectorFeature(layerId, feature, color, visibility, mapId, textField = "name") {
let sourceId = `vector-${layerId}`;
// symbol
// Slot 是 Mapbox 样式规范中的一个概念,它定义了图层在渲染过程中的位置和顺序。
let slot = "foreground";
let layout = {
"symbol-placement": "point",
"text-rotation-alignment": "auto",
"text-field": ["get", textField],
"text-font": ["literal", ["Arial Unicode MS Regular", "DIN Offc Pro Italic", "Open Sans Regular"]],
"text-size": 12,
"text-variable-anchor": ["top", "bottom", "left", "right"],
"text-radial-offset": 1,
"text-justify": "auto",
"text-optional": true,
"text-allow-overlap": false,
"text-ignore-placement": false,
'symbol-spacing': 1000, // 像素间距,增大值可减少标注密度
// 'text-max-angle': 300, // 角度越大,标注越少
"icon-optional": true,
"icon-allow-overlap": false,
"icon-ignore-placement": false,
"icon-image": "dot.png",
"icon-size": 0.5,
"visibility": visibility
};
let paint = {
// "text-color": color,
"text-halo-color": "#FFFFFF",
"text-halo-width": 0.1,
"icon-opacity": 1,
"text-color": [
"case",
["boolean", ["feature-state", "click"], false],
"red",
["boolean", ["feature-state", "hover"], false],
"yellow",
color
],
// slot: "middle"
};
if (feature === "line") {
slot = "top";
layout = {
"visibility": visibility,
// "line-join": "round",
// "line-cap": "round"
};
paint = {
"line-opacity": 1,
"line-width": [
"case",
["boolean", ["feature-state", "hover"], false],
4,
["boolean", ["feature-state", "click"], false],
2,
1.8
],
"line-color": [
"case",
["boolean", ["feature-state", "click"], false],
"red",
["boolean", ["feature-state", "hover"], false],
"yellow",
color
]
}
}
let layer = Vue.config.maps[mapId].getLayer(`${sourceId}-${feature}`);
// console.log(">>>>>>>>>>>>>>>>>>source-layer: ", layerId);
if (!layer) {
// console.log("Adding vector layer:", `${sourceId}-${feature}`);
Vue.config.maps[mapId].addLayer({
id: `${sourceId}-${feature}`,
'type': feature,
'source': sourceId,
'source-layer': layerId,
'layout': layout,
'paint': paint,
// 'slot': 'foreground'
});
}
// 点击可交互的图层,显示相关信息
await setHighlight(`${sourceId}-${feature}`, sourceId, mapId);
changeCursor(`${sourceId}-${feature}`, mapId);
if (layerId.includes("sample_line"))
await showSampleLineInfo(`${sourceId}-${feature}`, mapId);
if (layerId.includes("sample_station"))
await showSampleStationInfo(`${sourceId}-${feature}`, mapId);
else
await showTrackInfo(`${sourceId}-${feature}`, mapId);
return sourceId;
}
export function setHighlight(layerId, sourceId, mapId = "homeMap") {
let hoveredLineId = null;
let clickedLineId = null;
Vue.config.maps[mapId].on('click', layerId, (e) => {
if (!e.features || e.features.length <= 0)
return;
// // 查询点击点周围的所有要素(使用边界框)
// const bbox = [
// [e.point.x - 10, e.point.y - 10], // 左上
// [e.point.x + 10, e.point.y + 10] // 右下
// ];
// // 查询所有要素
// const allFeatures = Vue.config.maps[mapId].queryRenderedFeatures(bbox);
// console.log("allFeatures: ", allFeatures);
// // // 按图层分组
// // const featuresByLayer = groupFeaturesByLayer(allFeatures);
// // console.log('点击位置附近的所有要素:', featuresByLayer);
// console.log(e.features.length, e.features);
// console.log("1 e: ", e);
let feature = e.features[0];
if (e.features.length > 1 && e.options)
feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0];
sourceLayer = feature.sourceLayer;
// 获取当前单击的线的选择状态
let state = Vue.config.maps[mapId].getFeatureState({
source: sourceId,
sourceLayer: sourceLayer,
id: feature.id
});
if (clickedLineId) {
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: false });
}
clickedLineId = feature.id;
if (state.click === true) {
// 再次单击时取消选择
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: false });
// 隐藏信息框
// document.getElementById("cable-overlay")!.style.display = 'none';
// 过滤掉当前单击的线
clickedLineIds = clickedLineIds.filter((item) => item !== clickedLineId);
} else {
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: true });
// 显示信息框
// if (sourceId === 'GMSL Cable Data')
// document.getElementById("cable-overlay")!.style.display = 'block';
// 单击时加入选择列表(用于单击 ESC 键时取消选择状态)
clickedLineIds.push(clickedLineId);
// document.removeEventListener("keydown", removeHighlight, true);
document.addEventListener("keydown", (e) => {
removeHighlight(e, sourceId);
}, { once: true }); // once一个布尔值表示 listener 在添加之后最多只调用一次。如果为 truelistener 会在其被调用之后自动移除。
// }, true);
}
});
// When the user moves their mouse over the state-fill layer, we'll update the feature state for the feature under the mouse.
Vue.config.maps[mapId].on('mousemove', layerId, (e) => {
if (e.features.length <= 0)
return;
sourceLayer = e.features[0].sourceLayer;
if (hoveredLineId) {
Vue.config.maps[mapId].setFeatureState(
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
{ hover: false }
);
}
hoveredLineId = e.features[0].id;
Vue.config.maps[mapId].setFeatureState(
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
{ hover: true }
);
});
// When the mouse leaves the state-fill layer, update the feature state of the previously hovered feature.
Vue.config.maps[mapId].on('mouseleave', layerId, () => {
if (hoveredLineId && sourceLayer) {
Vue.config.maps[mapId].setFeatureState(
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
{ hover: false }
);
}
hoveredLineId = null;
});
}
export function changeCursor(layerId, mapId = "homeMap", cursor = "pointer") {
Vue.config.maps[mapId].on("mouseenter", layerId, () => {
Vue.config.maps[mapId].getCanvas().style.cursor = cursor;
});
Vue.config.maps[mapId].on("mouseleave", layerId, () => {
Vue.config.maps[mapId].getCanvas().style.cursor = "";
});
}
export function showSampleLineInfo(layerId, mapId = "homeMap") {
Vue.config.maps[mapId].on("click", layerId, async (e) => {
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
if (e.defaultPrevented)
return;
// 标记该 event 已触发,禁止点击事件穿透
if (typeof e.preventDefault === "function")
e.preventDefault();
const description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2">测线信息</td>
</tr>
</thead>
<tr>
<td>数据集名称: <b>${e.features[0].properties.dataset_name}</b></td>
<td>数据样品编号: <b>${e.features[0].properties.sample_name}</b></td>
</tr>
<tr>
<td>赞助机构: <b>${e.features[0].properties.funding_org}</b></td>
<td>公开期限: <b>${e.features[0].properties.publish_rule}</b></td>
</tr>
<tr>
<td>开始经度: <b>${e.features[0].properties.start_lon}°</b></td>
<td>结束经度: <b>${e.features[0].properties.end_lon}°</b></td>
</tr>
<tr>
<td>开始纬度: <b>${e.features[0].properties.start_lat}°</b></td>
<td>结束纬度: <b>${e.features[0].properties.end_lat}°</b></td>
</tr>
<tr>
<td>开始时间: <b>${e.features[0].properties.start_time}</b></td>
<td>结束时间: <b>${e.features[0].properties.end_time}</b></td>
</tr>
<tr>
<td>测线深度: <b>${e.features[0].properties.line_deep}m</b></td>
<td>数据状态: <b>${e.features[0].properties.sample_status}</b></td>
</tr>
<tr>
<td colspan="2">备注信息: <b>${e.features[0].properties.sample_remark}</b></td>
</tr>
</table>`;
showPopupDetails(mapId, e.lngLat, description, "450px");
});
}
export function showSampleStationInfo(layerId, mapId = "homeMap") {
Vue.config.maps[mapId].on("click", layerId, async (e) => {
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
if (e.defaultPrevented)
return;
// 标记该 event 已触发,禁止点击事件穿透
// if (typeof e.preventDefault === "function")
// e.preventDefault();
// console.log(e.features.length, e.features);
// console.log("2 e: ", e);
let feature = e.features[0];
if (e.features.length > 1)
feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0];
const description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2">站位信息</td>
</tr>
</thead>
<tr>
<td>数据集名称: <b>${feature.properties.dataset_name}</b></td>
<td>数据样品编号: <b>${feature.properties.sample_name}</b></td>
</tr>
<tr>
<td>赞助机构: <b>${feature.properties.funding_org}</b></td>
<td>公开期限: <b>${feature.properties.publish_rule}</b></td>
</tr>
<tr>
<td>站位经度: <b>${feature.properties.sampling_lon}°</b></td>
<td>站位纬度: <b>${feature.properties.sampling_lat}°</b></td>
</tr>
<tr>
<td>采样时间: <b>${feature.properties.sampling_time}</b></td>
<td>采样深度: <b>${feature.properties.sampling_deep}m</b></td>
</tr>
<tr>
<td colspan="2">备注信息: <b>${feature.properties.sample_remark}</b></td>
</tr>
</table>`;
showPopupDetails(mapId, feature.geometry.coordinates, description, "450px");
});
}
export function showTrackInfo(layerId, mapId = "homeMap") {
Vue.config.maps[mapId].on("click", layerId, async (e) => {
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
if (e.defaultPrevented)
return;
// 标记该 event 已触发,禁止点击事件穿透
if (typeof e.preventDefault === "function")
e.preventDefault();
let coordinate = e.lngLat;
let match = layerId.match(/vector-(.*)-[(symbol)|(line)]/);
if (!match)
return;
const voyageId = match[1];
regInfo({ voyage_name: voyageId }).then(res => {
const info = res.map.voyage;
const description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2"><a style="color:#0000FF" target="_balnk" title="点击查看航次详情" href="${Vue.config.baseUrl}/ds/#/container/dataDetail?voyage_name=${info.voyage_name}">航次信息</a></td>
</tr>
</thead>
<tr>
<td>航次编号: <b>${info.voyage_name}</b></td>
<td>航次首席: <b>${info.voyage_officer}</b></td>
</tr>
<tr>
<td>科考船舶: <b>${info.ship_name}</b></td>
<td>资助机构: <b>${info.funding_org}</b></td>
</tr>
<tr>
<td>离港日期: <b>${info.voyage_start_date}</b></td>
<td>返港日期: <b>${info.voyage_end_date}</b></td>
</tr>
<tr>
<td colspan="2">航次概况: <b>${info.voyage_remark}</b></td>
</tr>
</table>`;
showPopupDetails(mapId, coordinate, description, "420px");
})
});
}
export function showPopupDetails(mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}) {
const map = Vue.config.maps[mapId];
if (!map)
return;
const popupOptions = Object.assign({ offset: {}, className: "my-class", closeButton: closeButton, closeOnClick: closeOnClick }, options);
// 如果已有共享 popup复用并更新位置/内容;否则创建新的并绑定 close 事件以便释放引用
if (sharedPopup) {
try {
sharedPopup.setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth);
} catch (err) {
try {
// 万一现有 popup 状态异常,移除并重新创建
sharedPopup.remove();
sharedPopup = null;
} catch (e) {
console.error("移除异常 popup 失败:", e);
}
sharedPopup = new window.mapboxgl.Popup(popupOptions)
.setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth)
.addTo(map);
sharedPopup.on('close', () => { sharedPopup = null; });
}
} else {
sharedPopup = new window.mapboxgl.Popup(popupOptions)
.setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth)
.addTo(map);
sharedPopup.on('close', () => { sharedPopup = null; });
}
}
export async function loadJsonLineFeature(layerId, color, geojson, mapId) {
// console.log("layerId: ",layerId);
let source = Vue.config.maps[mapId].getSource(layerId);
if (!source) {
Vue.config.maps[mapId].addSource(layerId, {
"type": "geojson",
"data": geojson
});
}
else {
source.setData(geojson);
}
let layer = Vue.config.maps[mapId].getLayer(layerId);
if (layer)
Vue.config.maps[mapId].setLayoutProperty(layerId, "visibility", "visible");
else {
Vue.config.maps[mapId].addLayer({
'id': layerId,
'type': 'line',
'source': layerId,
'layout': {
'visibility': 'visible',
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': color,
'line-width': 1
}
});
// 点击可交互的图层,显示相关信息
await setHighlight(layerId, layerId, mapId);
changeCursor(layerId, mapId);
// await showTrackInfo(`${sourceId}-${feature}`, mapId);
}
}
export async function loadJsonPointFeature(layerId, color, geojson, text_field, mapId) {
let source = Vue.config.maps[mapId].getSource(layerId);
if (!source) {
Vue.config.maps[mapId].addSource(layerId, {
"type": "geojson",
"data": geojson
});
}
else {
source.setData(geojson);
}
let layer = Vue.config.maps[mapId].getLayer(layerId);
if (layer)
Vue.config.maps[mapId].setLayoutProperty(layerId, "visibility", "visible");
else {
Vue.config.maps[mapId].addLayer({
'id': layerId,
'type': 'symbol',
'source': layerId,
'layout': {
'icon-image': 'dot.png',
'icon-size': 0.5,
// get the title name from the source's "title" property
'text-field': ['get', text_field],
'text-font': [
'Open Sans Semibold',
'Arial Unicode MS Bold'
],
"text-size": 10,
'text-offset': [0, 1.25],
'text-anchor': 'top',
'visibility': 'visible'
},
'paint': {
"text-color": color,
"text-halo-color": "#FFFFFF",
"text-halo-width": 0.1,
"icon-opacity": 1,
}
});
// 点击可交互的图层,显示相关信息
await setHighlight(layerId, layerId, mapId);
changeCursor(layerId, mapId);
// await showTrackInfo(`${sourceId}-${feature}`, mapId);
}
}
export async function highlightFeaturesByProperty(mapId, sourceId, layerId, propertyName = "sample_name", targetValue = "TARGET_VALUE") {
// 清空所有高亮状态(可选)
Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(f => {
console.log("AAAAA: ", f);
Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: false });
});
// 标记匹配要素
Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(f => {
console.log("BBBBB: ", f);
if (f.properties && f.properties[propertyName] === targetValue) {
Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: true });
}
});
// 图层 paint 使用 feature-state
Vue.config.maps[mapId].setPaintProperty(layerId, 'text-color', [
'case',
['boolean', ['feature-state', 'highlight'], false],
'#FFFFFF', // 高亮颜色
"#FF0000"// 默认颜色
]);
}

View File

@ -26,7 +26,7 @@
</el-radio-group>
</div>
<div class="left4-sample-wrap" v-if="radio == '数据样品集'">
<p class="rule-text">编号规则:潜次号_平台_设备类型_样品数据类型</p>
<p class="rule-text">编号规则:航次号_潜次号_设备类型_数据样品类型</p>
<div class="left4">
<div v-for="(item, index) in dataList" :key="index" :id="item.tsy_id" class="left4_1" >
<div class="left4_top">
@ -35,7 +35,7 @@
</div>
<ul v-show="currenId == item.tsy_id && fold">
<li :class="sample.tsy_id == currenSample ? 'bg-color' : ''" class="sample" :id="sample.tsy_id"
v-for="(sample, i) in item.sampleArray" :key="i" @click.stop="onSample(sample)">{{ sample.sample_name }}
v-for="(sample, i) in item.sampleArray" :key="i" @click.stop="onSample(sample, item.job_type)">{{ sample.sample_name }}
</li>
</ul>
<div class="left4_list">
@ -83,7 +83,8 @@
<div class="left4_top">
<p :style="currentVoyage === item.tsy_id ? 'color: #409eff' : ''">
<span style="color: #409eff" @click="turnDeatilPage(item)">{{ item.voyage_name }}</span>
<span class="mileage">{{ item.voyage_mileage }}海里</span></p>
<span class="mileage">{{ item.voyage_mileage }}海里</span>
</p>
<span class="org">{{ item.funding_org }}</span>
</div>
<div class="left4_list">
@ -116,11 +117,15 @@
<span>{{ item.serect_status == 'Active' ? '是' : '否' }}</span>
</div>
<div class="btns">
<el-button v-if="!item.highlight" icon="el-icon-s-opportunity" circle @click="addHighlightLayer(item.voyage_name, index)" />
<el-button v-if="item.highlight" type="primary" icon="el-icon-s-opportunity" circle @click="clearHighlight" />
<el-button v-if="!item.show" icon="el-icon-view" circle @click="addSingleLineImg(item.voyage_name, index)" />
<el-button v-if="item.show" type="primary" icon="el-icon-view" circle @click="removeSingleLineImg(item.voyage_name, index)" />
<el-button icon="el-icon-location-information" circle />
<el-button v-if="!item.highlight" icon="el-icon-s-opportunity" circle
@click="addHighlightLayer(item.voyage_name, index)" />
<el-button v-if="item.highlight" type="primary" icon="el-icon-s-opportunity" circle
@click="clearHighlight(item.voyage_name, index)" />
<el-button v-if="!item.show" icon="el-icon-view" circle
@click="addSingleLineImg(item.voyage_name, index)" />
<el-button v-if="item.show" type="primary" icon="el-icon-view" circle
@click="removeSingleLineImg(item.voyage_name, index)" />
<!-- <el-button icon="el-icon-location-information" circle /> -->
</div>
</div>
</div>
@ -133,65 +138,53 @@
<img src="../../static/images/arrow_nav1.png" alt="">
</div>
<div class="right">
<div id="cesiumContainerData" style="height: 100vh;width:100%;" />
<div id="dataSearchMap" style="height: 100vh;width:100%;" />
<a @click="goHome"> <img class="logo" src="../../static/images/logo_map.png" alt="">
<p class="goHome">返回首页</p>
</a>
</div>
</div>
<!-- 航次名称弹窗-->
<el-dialog
title="请选择航次名称"
:visible.sync="dialog1"
width="1000px"
>
<el-table ref="tableData1" border highlight-current-row @current-change="handleCurrentChange1" max-height="480" :data="tableData1" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-dialog title="请选择航次名称" :visible.sync="dialog1" width="1000px">
<el-table ref="tableData1" border highlight-current-row @current-change="handleCurrentChange1" max-height="480"
:data="tableData1" :header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }">
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="name" label="航次名称" />
</el-table>
</el-dialog>
<!-- 平台类型弹窗-->
<el-dialog
title="请选择平台类型"
:visible.sync="dialog2"
width="1000px"
>
<el-table ref="tableData2" border @current-change="handleCurrentChange2" :data="tableData2" max-height="480" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-dialog title="请选择平台类型" :visible.sync="dialog2" width="1000px">
<el-table ref="tableData2" border @current-change="handleCurrentChange2" :data="tableData2" max-height="480"
:header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }">
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="name" label="平台类型名称" />
<el-table-column prop="value" label="英文类型名称" />
</el-table>
</el-dialog>
<!-- 设备类型弹窗-->
<el-dialog
title="请选择设备类型"
:visible.sync="dialog3"
width="1000px"
>
<el-table ref="tableData3" border @current-change="handleCurrentChange3" :data="tableData3" max-height="480" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-dialog title="请选择设备类型" :visible.sync="dialog3" width="1000px">
<el-table ref="tableData3" border @current-change="handleCurrentChange3" :data="tableData3" max-height="480"
:header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }">
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="name" label="设备类型名称" />
<el-table-column prop="value" label="英文名称" />
</el-table>
</el-dialog>
<!-- 数据样品类型弹窗-->
<el-dialog
title="请选择数据样品类型"
:visible.sync="dialog4"
width="1000px"
>
<el-table ref="tableData4" border @current-change="handleCurrentChange4" :data="tableData4" max-height="480" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
<el-dialog title="请选择数据样品类型" :visible.sync="dialog4" width="1000px">
<el-table ref="tableData4" border @current-change="handleCurrentChange4" :data="tableData4" max-height="480"
:header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }">
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="name" label="数据样品类型类型名称" />
<el-table-column prop="value" label="英文名称" />
</el-table>
</el-dialog>
<!-- 航次信息弹窗-->
<el-dialog
title="航次信息"
:visible.sync="dialog5"
width="800px"
>
<el-dialog title="航次信息" :visible.sync="dialog5" width="800px">
<div class="detail_b">
<h3 style="padding-bottom: 10px;">{{ voyageInfo.voyage_name }}</h3>
<p>{{ voyageInfo.voyage_remark }}</p>
@ -241,6 +234,10 @@ import Cookies from 'js-cookie'
import {
setImageryViewModels
} from '@/utils/common'
import Vue from 'vue'
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
const Cesium = window.Cesium
export default {
@ -249,6 +246,7 @@ export default {
return {
show: false,
viewer: null,
filter: null,
color: [Cesium.Color.RED, Cesium.Color.GREEN, Cesium.Color.BLUE, Cesium.Color.CHARTREUSE, Cesium.Color.DARKBLUE],
searchType: [
{
@ -362,8 +360,18 @@ export default {
},
},
mounted() {
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
this.initCesium()
initMapbox("dataSearchMap", {
tileKey: "world_horizon_1",
zoom: 4,
center: [122, 15],
minZoom: 2,
maxZoom: 29,
coastlineOn:false,
contourOn:false
});
// this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
// this.initCesium()
},
methods: {
//
@ -453,21 +461,35 @@ export default {
const arr = res.page.records
if (this.radio === '航次') {
if (arr.length) {
let i = 0;
const colors = ColorGenerator.generateDivergingColors(arr.length);
arr.forEach(item => {
item.highlight = false
item.show = true
this.addSingleLineImg(item.voyage_name, null)
// this.addSingleLineImg(item.voyage_name, null)
//
loadVectorLayer(item.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "dataSearchMap", "dsds");
i++;
})
}
this.dataList = arr
}
if (this.radio === '数据样品集') {
try {
this.getDataPoints(arr)
}
catch (err) {
console.log(err);
}
}
})
},
//
addHighlightLayer(voyageName, i) {
Vue.config.maps["dataSearchMap"].setPaintProperty(`vector-${voyageName}-line`, "line-width", 4);
this.dataList[i].highlight = true;
return;
this.clearHighlight()
const highlightLayer = new Cesium.WebMapTileServiceImageryProvider({
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}-highlight/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
@ -482,7 +504,19 @@ export default {
this.dataList[i].highlight = true;
},
//
clearHighlight() {
clearHighlight(voyageName, i) {
let lineWidthExpression = [
"case",
["boolean", ["feature-state", "hover"], false],
4,
["boolean", ["feature-state", "click"], false],
2,
1.8
];
Vue.config.maps["dataSearchMap"].setPaintProperty(`vector-${voyageName}-line`, "line-width", lineWidthExpression);
this.dataList[i].highlight = false;
return;
this.dataList.map(item => { item.highlight = false })
//
if (this.highlightLayer) {
@ -492,7 +526,13 @@ export default {
},
//
addSingleLineImg(voyageName, i) {
if (i !== null) {
this.dataList[i].show = true;
}
//
Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-symbol`, "visibility", "visible");
Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-line`, "visibility", "visible");
return;
//
if (this.trackLayerMap[voyageName]) {
console.warn(`航次 ${voyageName} 的轨迹图层已存在`);
@ -519,6 +559,16 @@ export default {
//
removeSingleLineImg(voyageName, i) {
if (i !== null) {
this.dataList[i].show = false;
}
//
// let visibility = Vue.config.maps["dataSearchMap"].getLayoutProperty(`vector-${voyageName}-symbol`, "visibility");
// visibility = visibility == "visible" ? "none" : "visible";
Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-symbol`, "visibility", "none");
Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-line`, "visibility", "none");
return;
const layerInstance = this.trackLayerMap[voyageName];
if (layerInstance) {
// CesiumimageryLayers
@ -582,7 +632,7 @@ export default {
},
//
clearAll() {
this.clearHighlight()
// this.clearHighlight()
if (this.trackLayerMap.length) {
this.trackLayerMap.forEach(layer => {
this.viewer.imageryLayers.remove(layer);
@ -598,9 +648,20 @@ export default {
//
getDataPoints(arr) {
if (!arr.length) return false
let i = 0;
const colors = ColorGenerator.generateDivergingColors(arr.length);
arr.forEach(item => {
this.addSingleLineImg(item.voyage_name, null)
this.divingImage(item)
// 线/
if (item.job_type == "测线作业") {
loadVectorLayer(`sample_line_${item.dataset_name}`, ["line"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearchMap", "dsds", "sample_name");
} else {
loadVectorLayer(`sample_station_${item.dataset_name}`, ["symbol"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearchMap", "dsds", "sample_name");
}
i++;
// this.addSingleLineImg(item.voyage_name, null)
// this.dataList[i].show = true;
// this.divingImage(item)
})
const voyageList = arr
voyageList.map(item => {
@ -610,11 +671,11 @@ export default {
dataset_id: item.tsy_id
}).then(res => {
item.sampleArray = res.array
if (res.array.length) {
res.array.forEach(sample => {
this.sampleLineImage(sample)
})
}
// if (res.array.length) {
// res.array.forEach(sample => {
// this.sampleLineImage(sample)
// })
// }
})
}
if (item.job_type === '站位作业') {
@ -624,22 +685,21 @@ export default {
}).then(res => {
item.sampleArray = res.array
//
if (res.array.length) {
res.array.forEach(sample => {
this.sampleLineImage(sample)
})
}
// if (res.array.length) {
// res.array.forEach(sample => {
// this.sampleLineImage(sample)
// });
// }
})
}
})
this.dataList = voyageList
console.log(this.dataList)
},
// cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
this.viewer = new Cesium.Viewer('cesiumContainerData', {
this.viewer = new Cesium.Viewer('dataSearchMap', {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -712,7 +772,7 @@ export default {
// 6000线
const contour6000 = new Cesium.WebMapTileServiceImageryProvider(
{
url: this.CesiumHostAddr + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
url: Vue.config.baseUrl + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
layer: 'ougp:contour6000',
style: 'default',
format: 'image/png',
@ -739,9 +799,17 @@ export default {
},
shousuo() {
this.show = true
// resize
setTimeout(() => {
Vue.config.maps["dataSearchMap"].resize();
}, 10);
},
toshow() {
this.show = false
// resize
setTimeout(() => {
Vue.config.maps["dataSearchMap"].resize();
}, 10);
},
//
openList(item, type) {
@ -766,8 +834,34 @@ export default {
}
},
//
onSample(sample) {
onSample(sample, job_type) {
this.currenSample = sample.tsy_id
if (job_type == "测线作业" && (!sample.start_lon || !sample.start_lat || !sample.end_lon || !sample.end_lat) || job_type == "站位作业" && (!sample.sampling_lon || !sample.sampling_lat)) {
alert('该样品无坐标信息,无法定位!');
return;
}
let lon = 0;
let lat = 0;
if (job_type == "测线作业") {
// lon = Number(sample.start_lon);
// lat = Number(sample.start_lat);
lon = (Number(sample.start_lon) + Number(sample.end_lon)) / 2;
lat = (Number(sample.start_lat) + Number(sample.end_lat)) / 2;
}
if (job_type == "站位作业") {
lon = Number(sample.sampling_lon);
lat = Number(sample.sampling_lat);
}
Vue.config.maps["dataSearchMap"].setZoom(10);
Vue.config.maps["dataSearchMap"].flyTo({
center: [lon, lat],
speed: 10
});
let layerId = `vector-${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}-${job_type == "测线作业" ? "line" : "symbol"}`;
let sourceLayerId = `${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}`;
// vector-sample_station_TS-46-1_FDZ377_Slurp_SWYP-symbolvector-sample_line_TS2-29-1_SY612_GQXJ_PJZP-line
// highlightFeaturesByProperty("dataSearchMap", `${job_type == "线" ? "sample_line" : "sample_station"}_${sample.dataset_name}`, layerId, "sample_name", sample.sample_name);
triggerLayerClick("dataSearchMap", sourceLayerId, layerId, [lon, lat], { "sample_name": sample.sample_name });
},
goHome() {
this.$router.push({ name: 'home' })
@ -783,6 +877,18 @@ export default {
</script>
<style scoped lang="scss">
// #dataSearchMap {
// // position: absolute;
// left: 0px;
// top: 0px;
// bottom: 0px;
// right: 0px;
// padding: 0px;
// margin: 0px;
// width: 100%;
// height: 100vh;
// }
.shuju {
width: 100%;
height: 100%;

View File

@ -63,37 +63,20 @@
<img src="../../static/images/guiji.png" alt="">
</div>
<div class="guiji_2">
<div id="cesiumContainer" ref="cesiumContainer"
onmouseleave="document.getElementById('mouseWheelTip').style.display = 'none'"
style="height: 100%;width:100%;">
<div id="homeMap" ref="homeMap" onmouseleave="document.getElementById('mouseWheelTip').style.display = 'none'"
onmouseenter="document.getElementById('mouseWheelTip').style.display = ''" style="height: 100%;width:100%;">
<div class="son" style="display: none;">弹框插槽</div>
<div id="mouseWheelTip" style="display: none;">地图缩放按下 Ctrl + 鼠标滚轮</div>
<el-popover
placement="left-start"
title="航次列表"
width="300"
trigger="hover"
>
<el-popover placement="left-start" title="航次列表" width="300" trigger="hover">
<div class="contour-line">
<span>等深线</span>
<el-button
size="mini"
v-for="depth in depthOptions"
:key="depth"
:type="selectedDepths.includes(depth) ? 'primary' : ''"
@click="toggleDepth(depth)"
>
<el-button size="mini" v-for="depth in depthOptions" :key="depth"
:type="selectedDepths.includes(depth) ? 'primary' : ''" @click="toggleDepth(depth)">
{{ depth }}
</el-button>
</div>
<el-table
ref="multipleTable"
:data="voyageList"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
highlight-current-row
:current-row-key="currentVoyage"
>
<el-table ref="multipleTable" :data="voyageList" @selection-change="handleSelectionChange"
@row-click="handleRowClick" highlight-current-row :current-row-key="currentVoyage">
<el-table-column type="selection" width="50" />
<el-table-column property="voyage_name" label="航次" />
</el-table>
@ -245,6 +228,7 @@
</template>
<script>
import Vue from 'vue'
import Footer from '../components/public/Footer'
import { getVoyage, statisticsNums } from '../api/home'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
@ -253,6 +237,9 @@ import { sampleDatasetList, voyageRegList } from '@/api/dataTransfer'
import {
modifyCesiumMouseWheel, setImageryViewModels
} from '@/utils/common'
import { loadVectorLayer } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
const Cesium = window.Cesium
@ -335,8 +322,28 @@ export default {
this.sampleDataUpdate()
},
mounted() {
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
this.initCesium()
initMapbox("homeMap", {
tileKey: "World_Imagery",
zoom: 4,
center: [122, 15],
minZoom: 2,
maxZoom: 29,
coastlineOn: false,
contourOn: false
});
Vue.config.maps["homeMap"].on('load', () => {
handleWheel("homeMap");
Vue.config.maps["homeMap"].scrollZoom.disable();
// 线
// loadVectorLayer("GSHHS_f_L1", ["line"], ["#FEFEFE"], "homeMap", 'ougp');
// 线
loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "none", "homeMap", 'ougp');
loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "none", "homeMap", 'ougp');
});
// this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
},
methods: {
//
@ -345,7 +352,16 @@ export default {
start: 0,
limit: 10
}).then(res => {
this.voyageList = res.page.records
this.voyageList = res.page.records;
let i = 0;
const colors = ColorGenerator.generateDivergingColors(this.voyageList.length);
this.voyageList.forEach(element => {
//
loadVectorLayer(element.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "homeMap", "dsds");
i++;
});
this.$nextTick(() => {
//
this.$refs.multipleTable.toggleAllSelection()
@ -356,7 +372,7 @@ export default {
// cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
this.viewer = new Cesium.Viewer('cesiumContainer', {
this.viewer = new Cesium.Viewer('homeMap', {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -432,6 +448,14 @@ export default {
},
//
handleRowClick(row, column, event) {
// /
let visibility = Vue.config.maps["homeMap"].getLayoutProperty(`vector-${row.voyage_name}-symbol`, "visibility");
visibility = visibility == "visible" ? "none" : "visible";
Vue.config.maps["homeMap"].setLayoutProperty(`vector-${row.voyage_name}-symbol`, "visibility", visibility);
Vue.config.maps["homeMap"].setLayoutProperty(`vector-${row.voyage_name}-line`, "visibility", visibility);
this.$refs.multipleTable.toggleRowSelection(row);
return;
//
if (this.currentVoyage === row.voyage_name) {
this.clearHighlight();
@ -492,7 +516,9 @@ export default {
//
voyagesToRemove.forEach(voyage => {
this.removeSingleLineImg(voyage);
// this.removeSingleLineImg(voyage);
Vue.config.maps["homeMap"].setLayoutProperty(`vector-${voyage}-symbol`, "visibility", "none");
Vue.config.maps["homeMap"].setLayoutProperty(`vector-${voyage}-line`, "visibility", "none");
});
//
@ -500,7 +526,9 @@ export default {
// val
const correspondingItem = val.find(item => item.voyage_name === voyage);
if (correspondingItem) {
this.addSingleLineImg(correspondingItem);
// this.addSingleLineImg(correspondingItem);
Vue.config.maps["homeMap"].setLayoutProperty(`vector-${voyage}-symbol`, "visibility", "visible");
Vue.config.maps["homeMap"].setLayoutProperty(`vector-${voyage}-line`, "visibility", "visible");
}
});
@ -517,7 +545,6 @@ export default {
return;
}
//
const trackLine = new Cesium.WebMapTileServiceImageryProvider({
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
layer: `dsds:${voyageName}`,
@ -552,10 +579,12 @@ export default {
const index = this.selectedDepths.indexOf(depth);
if (index > -1) {
this.selectedDepths.splice(index, 1); //
this.removeSingleContourLine(depth); // 线
// this.removeSingleContourLine(depth); // 线
Vue.config.maps["homeMap"].setLayoutProperty(`vector-contour-${depth}m-line`, "visibility", "none");
} else {
this.selectedDepths.push(depth); //
this.addSingleContourLine(depth); // 线
// this.addSingleContourLine(depth); // 线
Vue.config.maps["homeMap"].setLayoutProperty(`vector-contour-${depth}m-line`, "visibility", "visible");
}
},
// 线

View File

@ -44,59 +44,34 @@
<div class="map-module">
<div class="bezel">
<div class="map-title">下潜单位</div>
<div id="auvBaiduCesiumContainer" ref="auvBaiduCesiumContainer" style="height: 100%;width:100%;" />
<div id="workUnitMap_auv" ref="workUnitMap_auv" style="height: 100%;width:100%;" />
</div>
<div class="bezel">
<div class="map-title">下潜地图</div>
<div id="auvCesiumContainer" ref="auvCesiumContainer" style="height: 100%;width:100%;" />
<div id="voyageMap_auv" ref="voyageMap_auv" style="height: 100%;width:100%;" />
</div>
</div>
<!-- echarts板块-->
<div class="echarts-module">
<div class="bezel">
<BarChart
:title="barData1.title"
:chart-data="barData1.salesData"
:categories="barData1.productCategories"
chart-type="single"
/>
<BarChart :title="barData1.title" :chart-data="barData1.salesData" :categories="barData1.productCategories"
chart-type="single" />
</div>
<div class="bezel">
<BarChart
:title="barData2.title"
:chart-data="barData2.salesData"
:categories="barData2.productCategories"
chart-type="stacked"
stack-name="新增单位数"
/>
<BarChart :title="barData2.title" :chart-data="barData2.salesData" :categories="barData2.productCategories"
chart-type="stacked" stack-name="新增单位数" />
</div>
<div class="bezel">
<BarChart
:title="barData3.title"
:chart-data="barData3.salesData"
:categories="barData3.productCategories"
/>
<BarChart :title="barData3.title" :chart-data="barData3.salesData" :categories="barData3.productCategories" />
</div>
<div class="bezel">
<BarChart
:title="barData4.title"
:chart-data="barData4.salesData"
:categories="barData4.productCategories"
/>
<BarChart :title="barData4.title" :chart-data="barData4.salesData" :categories="barData4.productCategories" />
</div>
<div class="bezel">
<BarChart
:title="barData5.title"
:chart-data="barData5.salesData"
:categories="barData5.productCategories"
/>
<BarChart :title="barData5.title" :chart-data="barData5.salesData" :categories="barData5.productCategories" />
</div>
<div class="bezel">
<PieChart
:chart-data="pieData"
title="下潜单位类型"
chart-type="ring"
/>
<PieChart :chart-data="pieData" title="下潜单位类型" chart-type="ring" />
</div>
</div>
</div>
@ -111,6 +86,9 @@ import PieChart from './components/PieChart.vue'
import { setImageryViewModels } from '../../utils/common'
import { reportList, platformAuv, auvUnit, auvUnitYear, auvDiving } from '../../api/statistics'
import { filterDictItems } from '../../utils/index'
import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
const Cesium = window.Cesium
export default {
@ -168,9 +146,24 @@ export default {
this.getChartBar({ year: '2025' })
},
mounted() {
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
this.initCesium() //
this.initBaiduCesium() //
initMapbox("workUnitMap_auv", {
tileKey: "gaode",
zoom: 3,
center: [110, 31],
minZoom: 2,
maxZoom: 29
});
initMapbox("voyageMap_auv", {
tileKey: "GEBCO_basemap_NCEI",
zoom: 4,
center: [113, 15],
minZoom: 2,
maxZoom: 29
});
// this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
// this.initCesium() //
// this.initBaiduCesium() //
},
beforeDestroy() {
// Cesium
@ -217,11 +210,32 @@ export default {
//
getAuvUnit() {
auvUnit().then(res => {
//
this.clearBaiduCesiumMarkers()
let geojson = {
type: "FeatureCollection",
features: []
};
res.array.forEach(element => {
geojson.features.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [element.unit_lon, element.unit_lat]
},
properties: {
unit_name: element.unit_name,
unit_type: element.unit_type,
create_time: element.create_time,
tsy_id: element.tsy_id
}
});
});
loadJsonPointFeature("workUnit", "blue", geojson, "unit_name", "workUnitMap_auv");
//
this.createBaiduCesiumMarkers(res.array)
// //
// this.clearBaiduCesiumMarkers()
// //
// this.createBaiduCesiumMarkers(res.array)
const typeCount = res.array.reduce((acc, unit) => {
const type = unit.unit_type
acc[type] = (acc[type] || 0) + 1 // +11
@ -285,15 +299,21 @@ export default {
//
gitAuvDiving(params = {}) {
auvDiving(params).then(res => {
let i = 0;
const colors = ColorGenerator.generateDivergingColors(res.array.length);
//
res.array.forEach(item => {
this.addSingleLineImg(item)
//
loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_auv", "dsds");
i++;
// this.addSingleLineImg(item)
})
})
},
// Cesium -
initBaiduCesium() {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.auvBaiduCesiumContainer, {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.workUnitMap_auv, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -329,7 +349,7 @@ export default {
// cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
this.viewer = new Cesium.Viewer(this.$refs.auvCesiumContainer, {
this.viewer = new Cesium.Viewer(this.$refs.voyageMap_auv, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -534,24 +554,29 @@ export default {
flex-direction: column;
justify-content: center;
align-items: center;
.value {
font-size: 56px;
font-weight: bold;
.unit {
font-size: 26px;
}
}
.label {
font-size: 18px;
}
}
}
}
.map-module {
padding: 50px 50px;
display: flex;
justify-content: space-between;
gap: 50px;
.bezel {
flex: 1;
width: 100%;
@ -569,6 +594,7 @@ export default {
z-index: 8888;
font-size: 22px;
}
.map-year {
position: absolute;
top: 20px;
@ -579,6 +605,7 @@ export default {
}
}
}
.echarts-module {
padding: 0 50px 50px;
display: grid;

View File

@ -44,11 +44,11 @@
<div class="map-module">
<div class="bezel">
<div class="map-title">下潜单位</div>
<div id="baiduCesiumContainer" ref="baiduCesiumContainer" style="height: 100%;width:100%;" />
<div id="workUnitMap_hov" ref="workUnitMap_hov" style="height: 100%;width:100%;" />
</div>
<div class="bezel">
<div class="map-title">下潜地图</div>
<div id="cesiumContainer" ref="cesiumContainer" style="height: 100%;width:100%;" />
<div id="voyageMap_hov" ref="voyageMap_hov" style="height: 100%;width:100%;" />
</div>
</div>
<!-- echarts板块-->
@ -108,6 +108,9 @@ import PieChart from './components/PieChart.vue'
import { setImageryViewModels } from '../../utils/common'
import { reportList, platformHov, hovUnit, hovUnitYear, hovDiving } from '../../api/statistics'
import { filterDictItems } from '../../utils/index'
import {initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
const Cesium = window.Cesium
export default {
@ -165,9 +168,24 @@ export default {
this.getChartBar({ year: '2025' })
},
mounted() {
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
this.initCesium() //
this.initBaiduCesium() //
initMapbox("workUnitMap_hov", {
tileKey: "gaode",
zoom: 3,
center: [110, 31],
minZoom: 2,
maxZoom: 29
});
initMapbox("voyageMap_hov", {
tileKey: "GEBCO_basemap_NCEI",
zoom: 4,
center: [113, 15],
minZoom: 2,
maxZoom: 29
});
// this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
// this.initCesium() //
// this.initBaiduCesium() //
},
beforeDestroy() {
// Cesium
@ -214,11 +232,32 @@ export default {
//
getHovUnit() {
hovUnit().then(res => {
//
this.clearBaiduCesiumMarkers()
let geojson = {
type: "FeatureCollection",
features: []
};
res.array.forEach(element => {
geojson.features.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [element.unit_lon, element.unit_lat]
},
properties: {
unit_name: element.unit_name,
unit_type: element.unit_type,
create_time: element.create_time,
tsy_id: element.tsy_id
}
});
});
loadJsonPointFeature("workUnit", "blue", geojson, "unit_name", "workUnitMap_hov");
//
this.createBaiduCesiumMarkers(res.array)
// //
// this.clearBaiduCesiumMarkers()
// //
// this.createBaiduCesiumMarkers(res.array)
const typeCount = res.array.reduce((acc, unit) => {
const type = unit.unit_type
acc[type] = (acc[type] || 0) + 1 // +11
@ -282,15 +321,21 @@ export default {
//
gitHovDiving(params = {}) {
hovDiving(params).then(res => {
let i = 0;
const colors = ColorGenerator.generateDivergingColors(res.array.length);
//
res.array.forEach(item => {
this.addSingleLineImg(item)
//
loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_hov", "dsds");
i++;
// this.addSingleLineImg(item)
})
})
},
// Cesium -
initBaiduCesium() {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.baiduCesiumContainer, {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.workUnitMap_hov, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -326,7 +371,7 @@ export default {
// cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
this.viewer = new Cesium.Viewer('cesiumContainer', {
this.viewer = new Cesium.Viewer('voyageMap_hov', {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,

View File

@ -44,59 +44,34 @@
<div class="map-module">
<div class="bezel">
<div class="map-title">下潜单位</div>
<div id="rovBaiduCesiumContainer" ref="rovBaiduCesiumContainer" style="height: 100%;width:100%;" />
<div id="workUnitMap_rov" ref="workUnitMap_rov" style="height: 100%;width:100%;" />
</div>
<div class="bezel">
<div class="map-title">下潜地图</div>
<div id="rovCesiumContainer" ref="rovCesiumContainer" style="height: 100%;width:100%;" />
<div id="voyageMap_rov" ref="voyageMap_rov" style="height: 100%;width:100%;" />
</div>
</div>
<!-- echarts板块-->
<div class="echarts-module">
<div class="bezel">
<BarChart
:title="barData1.title"
:chart-data="barData1.salesData"
:categories="barData1.productCategories"
chart-type="single"
/>
<BarChart :title="barData1.title" :chart-data="barData1.salesData" :categories="barData1.productCategories"
chart-type="single" />
</div>
<div class="bezel">
<BarChart
:title="barData2.title"
:chart-data="barData2.salesData"
:categories="barData2.productCategories"
chart-type="stacked"
stack-name="新增单位数"
/>
<BarChart :title="barData2.title" :chart-data="barData2.salesData" :categories="barData2.productCategories"
chart-type="stacked" stack-name="新增单位数" />
</div>
<div class="bezel">
<BarChart
:title="barData3.title"
:chart-data="barData3.salesData"
:categories="barData3.productCategories"
/>
<BarChart :title="barData3.title" :chart-data="barData3.salesData" :categories="barData3.productCategories" />
</div>
<div class="bezel">
<BarChart
:title="barData4.title"
:chart-data="barData4.salesData"
:categories="barData4.productCategories"
/>
<BarChart :title="barData4.title" :chart-data="barData4.salesData" :categories="barData4.productCategories" />
</div>
<div class="bezel">
<BarChart
:title="barData5.title"
:chart-data="barData5.salesData"
:categories="barData5.productCategories"
/>
<BarChart :title="barData5.title" :chart-data="barData5.salesData" :categories="barData5.productCategories" />
</div>
<div class="bezel">
<PieChart
:chart-data="pieData"
title="下潜单位类型"
chart-type="ring"
/>
<PieChart :chart-data="pieData" title="下潜单位类型" chart-type="ring" />
</div>
</div>
</div>
@ -111,6 +86,9 @@ import PieChart from './components/PieChart.vue'
import { setImageryViewModels } from '../../utils/common'
import { reportList, platformRov, rovUnit, rovUnitYear, rovDiving } from '../../api/statistics'
import { filterDictItems } from '../../utils/index'
import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
const Cesium = window.Cesium
export default {
@ -168,9 +146,24 @@ export default {
this.getChartBar({ year: '2025' })
},
mounted() {
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
this.initCesium() //
this.initBaiduCesium() //
initMapbox("workUnitMap_rov", {
tileKey: "gaode",
zoom: 3,
center: [110, 31],
minZoom: 2,
maxZoom: 29
});
initMapbox("voyageMap_rov", {
tileKey: "GEBCO_basemap_NCEI",
zoom: 4,
center: [113, 15],
minZoom: 2,
maxZoom: 29
});
// this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
// this.initCesium() //
// this.initBaiduCesium() //
},
beforeDestroy() {
// Cesium
@ -217,11 +210,32 @@ export default {
//
getRovUnit() {
rovUnit().then(res => {
//
this.clearBaiduCesiumMarkers()
let geojson = {
type: "FeatureCollection",
features: []
};
res.array.forEach(element => {
geojson.features.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [element.unit_lon, element.unit_lat]
},
properties: {
unit_name: element.unit_name,
unit_type: element.unit_type,
create_time: element.create_time,
tsy_id: element.tsy_id
}
});
});
loadJsonPointFeature("workUnit", "blue", geojson, "unit_name", "workUnitMap_rov");
//
this.createBaiduCesiumMarkers(res.array)
// //
// this.clearBaiduCesiumMarkers()
// //
// this.createBaiduCesiumMarkers(res.array)
const typeCount = res.array.reduce((acc, unit) => {
const type = unit.unit_type
acc[type] = (acc[type] || 0) + 1 // +11
@ -285,15 +299,21 @@ export default {
//
gitRovDiving(params = {}) {
rovDiving(params).then(res => {
let i = 0;
const colors = ColorGenerator.generateDivergingColors(res.array.length);
//
res.array.forEach(item => {
this.addSingleLineImg(item)
//
loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_rov", "dsds");
i++;
// this.addSingleLineImg(item)
})
})
},
// Cesium -
initBaiduCesium() {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.rovBaiduCesiumContainer, {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.workUnitMap_rov, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -329,7 +349,7 @@ export default {
// cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
this.viewer = new Cesium.Viewer(this.$refs.rovCesiumContainer, {
this.viewer = new Cesium.Viewer(this.$refs.voyageMap_rov, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -534,24 +554,29 @@ export default {
flex-direction: column;
justify-content: center;
align-items: center;
.value {
font-size: 56px;
font-weight: bold;
.unit {
font-size: 26px;
}
}
.label {
font-size: 18px;
}
}
}
}
.map-module {
padding: 50px 50px;
display: flex;
justify-content: space-between;
gap: 50px;
.bezel {
flex: 1;
width: 100%;
@ -569,6 +594,7 @@ export default {
z-index: 8888;
font-size: 22px;
}
.map-year {
position: absolute;
top: 20px;
@ -579,6 +605,7 @@ export default {
}
}
}
.echarts-module {
padding: 0 50px 50px;
display: grid;

View File

@ -44,7 +44,7 @@
<div class="map-module">
<div class="bezel">
<div class="map-title">参航单位</div>
<div id="baiduCesiumContainer" ref="baiduCesiumContainer" style="height: 100%;width:100%;" />
<div id="workUnitMap_ship" ref="workUnitMap_ship" style="height: 100%;width:100%;" />
</div>
<div class="bezel">
<div class="map-title">航次地图</div>
@ -61,18 +61,14 @@
@change="selectVoyageYear"
/>
</div>
<div id="cesiumContainer" ref="cesiumContainer" style="height: 100%;width:100%;" />
<div id="voyageMap_ship" ref="voyageMap_ship" style="height: 100%;width:100%;" />
</div>
</div>
<!-- echarts板块-->
<div class="echarts-module">
<div class="bezel">
<BarChart
:title="barData1.title"
:chart-data="barData1.salesData"
:categories="barData1.productCategories"
chart-type="single"
/>
<BarChart :title="barData1.title" :chart-data="barData1.salesData" :categories="barData1.productCategories"
chart-type="single" />
</div>
<div class="bezel">
<BarChart
@ -92,36 +88,29 @@
/>
</div>
<div class="bezel">
<BarChart
:title="barData3.title"
:chart-data="barData3.salesData"
:categories="barData3.productCategories"
/>
<BarChart :title="barData3.title" :chart-data="barData3.salesData" :categories="barData3.productCategories" />
</div>
<div class="bezel">
<BarChart
:title="barData4.title"
:chart-data="barData4.salesData"
:categories="barData4.productCategories"
/>
<BarChart :title="barData4.title" :chart-data="barData4.salesData" :categories="barData4.productCategories" />
</div>
<div class="bezel">
<PieChart
:chart-data="pieData"
title="参航单位类型"
chart-type="ring"
/>
<PieChart :chart-data="pieData" title="参航单位类型" chart-type="ring" />
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import BarChart from './components/BarChart.vue'
import PieChart from './components/PieChart.vue'
import { setImageryViewModels } from '../../utils/common'
import { reportList, shipTotal, shipUnit, shipUnitYear, shipVoyage } from '../../api/statistics'
import { getYearRange, filterDictItems } from '../../utils/index'
import {initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
const Cesium = window.Cesium
export default {
@ -186,9 +175,23 @@ export default {
this.getChartBar({ year: '2025' })
},
mounted() {
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
this.initCesium() //
this.initBaiduCesium() //
initMapbox("workUnitMap_ship", {
tileKey: "gaode",
zoom: 3,
center: [110, 31],
minZoom: 2,
maxZoom: 29
});
initMapbox("voyageMap_ship", {
tileKey: "GEBCO_basemap_NCEI",
zoom: 4,
center: [113, 15],
minZoom: 2,
maxZoom: 29
});
// this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
// this.initBaiduCesium() //
// this.initCesium() //
},
beforeDestroy() {
// Cesium
@ -236,11 +239,31 @@ export default {
//
getShipUnit() {
shipUnit().then(res => {
//
this.clearBaiduCesiumMarkers()
let geojson = {
type: "FeatureCollection",
features: []
};
res.array.forEach(element => {
geojson.features.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [element.unit_lon, element.unit_lat]
},
properties: {
unit_name: element.unit_name,
unit_type: element.unit_type,
create_time: element.create_time,
tsy_id: element.tsy_id
}
});
});
loadJsonPointFeature("workUnit", "blue", geojson, "unit_name", "workUnitMap_ship");
//
this.createBaiduCesiumMarkers(res.array)
// //
// this.clearBaiduCesiumMarkers()
// //
// this.createBaiduCesiumMarkers(res.array)
const typeCount = res.array.reduce((acc, unit) => {
const type = unit.unit_type
@ -305,15 +328,22 @@ export default {
//
gitShipVoyage(params = {}) {
shipVoyage(params).then(res => {
let i = 0;
const colors = ColorGenerator.generateDivergingColors(res.array.length);
//
res.array.forEach(item => {
this.addSingleLineImg(item)
//
loadVectorLayer(item.voyage_name, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_ship", "dsds");
i++;
// this.addSingleLineImg(item)
})
})
},
// Cesium -
initBaiduCesium() {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.baiduCesiumContainer, {
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.workUnitMap_ship, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -349,7 +379,7 @@ export default {
// cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
this.viewer = new Cesium.Viewer('cesiumContainer', {
this.viewer = new Cesium.Viewer(this.$refs.voyageMap_ship, {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@ -566,24 +596,29 @@ export default {
flex-direction: column;
justify-content: center;
align-items: center;
.value {
font-size: 56px;
font-weight: bold;
.unit {
font-size: 26px;
}
}
.label {
font-size: 18px;
}
}
}
}
.map-module {
padding: 50px 50px;
display: flex;
justify-content: space-between;
gap: 50px;
.bezel {
flex: 1;
width: 100%;
@ -676,6 +711,7 @@ export default {
}
}
}
.echarts-module {
padding: 0 50px 50px;
display: grid;

View File

@ -443,7 +443,7 @@ export default {
this.ship = filterDictItems(res.array, '科考船')
this.hov = filterDictItems(res.array, '载人潜水器')
this.rov = filterDictItems(res.array, '遥控潜水器')
this.uuv = filterDictItems(res.array, '无人航行潜水器')
this.auv = filterDictItems(res.array, '无人航行潜水器')
})
},
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
static/sprites/Able.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/sprites/Bowditch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
static/sprites/Henson.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
static/sprites/Loyal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
static/sprites/aircraft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
static/sprites/arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

BIN
static/sprites/auv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

BIN
static/sprites/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
static/sprites/dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

BIN
static/sprites/dot_red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
static/sprites/fdz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
static/sprites/go.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

File diff suppressed because one or more lines are too long

BIN
static/sprites/naivemap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
static/sprites/ship.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/sprites/ship1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/sprites/ship11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/sprites/ship2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/sprites/ship3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/sprites/shipdt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/sprites/shys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
static/sprites/slow_sog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

File diff suppressed because one or more lines are too long

BIN
static/sprites/sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
static/sprites/stop_sog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

BIN
static/sprites/szy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB