WebGIS 数据可视化开发
Openlayers
npm 命令引入 OpenLayers 地图引擎
https://openlayers.org/ API文档 官方案例库
Quick Start
1.安装
Openlayers的npm包名为ol
npm install ol
CDN
<script src="https://cdn.jsdelivr.net/npm/ol@v7.1.0/dist/ol.js"></script>
<script src="https://lib.baomitu.com/openlayers/7.4.0/dist/ol.js"></script>
2.基本地图
import Map from 'ol/Map'
import View from 'ol/View'
import { Tile as TileLayer } from 'ol/layer'
import { XYZ } from 'ol/source'
// fromLonLat方法能将坐标从经度/纬度转换为其他投影
import { fromLonLat } from 'ol/proj'
// 拖拽旋转
import { defaults as defaultInteractions, DragRotateAndZoom } from 'ol/interaction'
init () {
// 高德 图层
// const tileLayer = new Tile({
// source: new XYZ({
// url: 'https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
// })
// })
// 天地图 图层 "http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=xxxxxxxxxxx";
var tileLayer = new TileLayer({
source: new XYZ({
url: "http://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=tkxxxxxxxxxxxxx",
}),
name: "BaseMap",
}),
this.map = new Map({
controls:[],//控件
layers: [tileLayer],
view: new View({
center: ol.proj.fromLonLat([113.7064,23.29071]), // 地图中心点
zoom: 15, // 缩放级别
minZoom: 0, // 最小缩放级别
maxZoom: 18, // 最大缩放级别
constrainResolution: true// 因为存在非整数的缩放级别,所以设置该参数为true来让每次缩放结束后自动缩放到距离最近的一个整数级别,这个必须要设置,当缩放在非整数级别时地图会糊
}),
target: this.$refs.olMap, // DOM容器
interactions: defaultInteractions().extend([new DragRotateAndZoom()]) // 按住shit旋转
})
}
for cdn
var url = "http://t0.tianditu.gov.cn/img_w/wmts?" +"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
"&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=xxxxxxxxxxxxxxx";
var layer =new ol.layer.Tile({
source: new ol.source.XYZ({
url: url,
}),
name: "BaseMap",
})
const map = new ol.Map({
target: 'mapDiv',
layers: [layer],
view: new ol.View({
center: ol.proj.fromLonLat([113.7064,23.29071]), // 地图中心点
zoom: 15, // 缩放级别
minZoom: 0,
maxZoom: 30,
constrainResolution: true
}),
});
// 绑定事件
var clickHandler = function (event) {
var coordinate = event.coordinate;
var lonLat = ol.proj.toLonLat(coordinate);
document.getElementById("tip").textContent = '坐标:[' +lonLat + ']'
};
map.on('click', clickHandler);3.加载WMS
import TileWMS from 'ol/source/TileWMS.js';
var wms = new TileLayer({
source: new TileWMS({
url : "http://121.32.129.19:9977/geoserver/ows",
crossOrigin: 'anonymous',
attributions: '自定义图层 ',
params :{
layers: 'sny:zcgy_1_ex',
Service: 'WMS',
Request: 'GetMap',
Version: '1.1.1',
format: 'image/png',
transparent:true,
},
serverType: 'mapserver',
}),
});
this.map.addLayer(wms)for cdn
var wmsLayer = new ol.layer.Tile({
source: new ol.source.TileWMS({
url:"http://192.168.40.112:8080/geoserver/ows",
crossOrigin: 'anonymous',
attributions: ' ',
params :{
layers: 'gbnt:xxxxxx',
Service: 'WMS',
Request: 'GetMap',
Version: '1.1.1',
format: 'image/png',
transparent:true,
},
serverType: 'mapserver',
}),
});
map.addLayer(wmsLayer)封装
/**
* WMS图层配置函数
* @param {Object} config - 配置对象
* @param {string} config.name - 图层名称
* @param {string} config.url - 服务地址
* @param {string} config.styles - 样式名称
* @param {string} config.cql_filter - CQL过滤器
* @param {Object} config.params - 额外的WMS参数
* @param {string} config.authorization - 认证头
* @param {Object} config.layerOptions - 图层选项
* @param {Object} config.sourceOptions - 数据源选项
* @returns {ol.layer.Tile} OpenLayers瓦片图层
*/
function getCustomWMSLayer(config) {
// 默认配置
const defaultConfig = {
url: "http://192.xxx.xxx.xxx:8080/geoserver/ows",
authorization: 'Basic xxxxx=',
transparent: true,
version: '1.1.1',
service: 'WMS',
request: 'GetMap',
serverType: 'geoserver',
crossOrigin: 'anonymous',
params: {},
layerOptions: {},
sourceOptions: {}
};
// 合并配置
const mergedConfig = Object.assign({}, defaultConfig, config);
// 构建WMS参数
const params = {
layers: mergedConfig.name,
Service: mergedConfig.service,
Request: mergedConfig.request,
Version: mergedConfig.version,
transparent: mergedConfig.transparent
};
// 添加可选参数
if(mergedConfig.styles) {
params.STYLES = mergedConfig.styles;
}
if(mergedConfig.cql_filter) {
params.CQL_FILTER = mergedConfig.cql_filter;
}
// 合并额外参数
Object.assign(params, mergedConfig.params);
// 创建数据源
const source = new ol.source.TileWMS(
Object.assign({
url: mergedConfig.url,
crossOrigin: mergedConfig.crossOrigin,
attributions: mergedConfig.attributions || ' ',
params: params,
serverType: mergedConfig.serverType,
// 自定义tileLoadFunction来处理认证
tileLoadFunction: function(tile, src) {
const customHeaders = mergedConfig.headers || {};
const authHeader = mergedConfig.authorization ?
{'Authorization': mergedConfig.authorization} : {};
fetch(src, {
headers: Object.assign({}, authHeader, customHeaders)
})
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
tile.getImage().src = url;
})
.catch(err => console.error('Tile load error:', err));
}
}, mergedConfig.sourceOptions)
);
// 创建图层
const layer = new ol.layer.Tile(
Object.assign({
source: source
}, mergedConfig.layerOptions)
);
return layer;
}
map.addLayer(getCustomWMSLayer({name: 'xxx:xxxxxxxxx', styles:'green'}))4.加载 WMTS
WMTS 的方式
WMTS方式遵从标准的OGC规范 参考官网的示例 https://openlayers.org/en/latest/examples/wmts.html
/**
* 创建支持自定义请求头(如 Authorization)的 GeoServer WMTS 图层
*
* @param {Object} config - 配置对象
* @param {string} [config.url='http://192.168.40.112:8080/geoserver/gwc/service/wmts'] - WMTS 服务地址
* @param {string} config.layer - 图层名称,例如 'sny:sz_ns_lzy_0828'
* @param {string} [config.matrixSet='EPSG:3857'] - 矩阵集
* @param {string} [config.format='image/png'] - 图像格式
* @param {string} [config.style=''] - 样式名称
* @param {number} [config.maxZoom=18] - 最大缩放级别(0 ~ maxZoom)
* @param {Object} [config.headers={}] - 自定义请求头,例如 { 'Authorization': 'Basic xxx' }
* @param {Object} [config.layerOptions={}] - ol.layer.Tile 的额外选项
* @param {Object} [config.sourceOptions={}] - ol.source.WMTS 的额外选项
* @returns {ol.layer.Tile}
*/
function getWMTSLayer(config) {
const defaultConfig = {
url: 'http://192.168.40.112:8080/geoserver/gwc/service/wmts',
layer: '',
matrixSet: 'EPSG:3857',
format: 'image/png',
style: '',
maxZoom: 18,
headers: {},
layerOptions: {},
sourceOptions: {}
};
const merged = Object.assign({}, defaultConfig, config);
if (!merged.layer) {
throw new Error('Missing required parameter: layer');
}
// 投影与切片方案
const projection = ol.proj.get('EPSG:3857');
const projectionExtent = projection.getExtent();
const size = ol.extent.getWidth(projectionExtent) / 256;
const resolutions = [];
const matrixIds = [];
const zoomLevels = merged.maxZoom + 1; // 0 到 maxZoom 共 maxZoom+1 级
for (let z = 0; z < zoomLevels; ++z) {
resolutions[z] = size / Math.pow(2, z);
// 注意:GeoServer RESTful 模式通常使用 "EPSG:3857:z" 格式的 matrixId
matrixIds[z] = merged.matrixSet + ':' + z;
}
// 构建 RESTful URL 模板
const extension = merged.format.split('/').pop(); // e.g., 'png' or 'jpeg'
const templateUrl = `${merged.url}/${merged.layer}/${merged.style}/{TileMatrix}/{TileCol}/{TileRow}.${extension}`;
// 创建 WMTS 数据源
const wmtsSource = new ol.source.WMTS(
Object.assign({
url: templateUrl,
layer: merged.layer,
matrixSet: merged.matrixSet,
format: merged.format,
projection: projection,
tileGrid: new ol.tilegrid.WMTS({
origin: ol.extent.getTopLeft(projectionExtent),
resolutions: resolutions,
matrixIds: matrixIds
}),
style: merged.style,
wrapX: true,
crossOrigin: 'anonymous',
// 支持自定义请求头的关键:重写 tileLoadFunction
tileLoadFunction: function(tile, src) {
fetch(src, {
headers: merged.headers
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(blob => {
const url = URL.createObjectURL(blob);
tile.getImage().src = url;
})
.catch(error => {
console.error('Failed to load WMTS tile:', error);
tile.setState(ol.TileState.ERROR);
});
}
}, merged.sourceOptions)
);
const layer = new ol.layer.Tile(
Object.assign({
source: wmtsSource
}, merged.layerOptions)
);
return layer;
}
var layer_Debug = new ol.layer.Tile({
source: new ol.source.TileDebug({
projection: projection,
tileGrid: wmtsSource.getTileGrid(),
})
})
map.addLayer(layer_Debug)
//项目影像图
var layer = getWMTSLayer({
layer: 'xxx:xxxxxxxxxxxxxxx',
headers: {
'Authorization': 'Basic d2ViX3VzZXI6WUpBS0wzUE92MHN2d3JIb2Y='
},
maxZoom: 25
});
map.addLayer(layer);
请求一直<ExceptionText>Unknown TILEMATRIX XX</ExceptionText> 异常
主要是以下三个参数有问题.
-
TileMatrix: 18 //需要带上 坐标系+层级参数 (EPSG:3857:18) 解决可参考: https://gis.stackexchange.com/questions/234756/openlayers-geoserver-how-to-force-tilematrix-prefix 需要自己算行列
-
TileRow: 113900 // 行列参数 超出范围 (计算不对?)
-
TileCol: 214878
XYZ 的方式
// geoserver wmts
var url =
"http://192.168.40.112:8080/geoserver/gwc/service/wmts?" +
"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.1.1&LAYER=sny:ss_zhnysfjd_180_planning"
+"&STYLE=&TILEMATRIXSET=EPSG:3857&FORMAT=image/png"
+"&TILEMATRIX=EPSG:3857:{z}&TILEROW={y}&TILECOL={x}";
var wmtsLayer = new ol.layer.Tile({
source: new ol.source.XYZ({//WMTS使用的数据源类型为ol.source.WMTS,XYZ方式使用的数据源类型为ol.source.XYZ;
url: url,
}),
name: "wmts",
});
//谷歌 影像图
var layer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}'
})
});
//天地图影像
var url = "http://t0.tianditu.gov.cn/img_w/wmts?" +
"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
"&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=c94ca7ff1fda359919b7132d1307ed62";
var layer =new ol.layer.Tile({
source: new ol.source.XYZ({
url: url,
}),
name: "BaseMap",
})
离线XYZ瓦片
import Map from 'ol/Map.js';
import XYZ from 'ol/source/XYZ.js';
import TileLayer from 'ol/layer/Tile.js';
const xyzSource = new XYZ({
crossOrigin: 'anonymous', tileUrlFunction: (zxy) => {
const [z, x, y] = zxy;
const yn = Math.pow(2, z) - y - 1;
//通过 WIM.getStaticResourcesBase64 方法
//瓦片路径地址为: map/{id}/{format.replaceAll("{x}",x).replaceAll("{y}",yn).replaceAll("{z}",z)}
//注意 format 为 x y z 占位符要替换
const formatUrl = format.replaceAll("{x}",x).replaceAll("{y}",yn).replaceAll("{z}",z)
const dataURL = WIM.getStaticResourcesBase64(`map/${id}/${formatUrl}`+) || '';
return 'data:image/png;base64,' + dataURL
}
})
const offlineMapLayer = new TileLayer({
zIndex: 3,
source: xyzSource
});
this.map.addLayer(offlineMapLayer);基本概念 Basic Concepts
https://openlayers.org/doc/tutorials/concepts.html
Map OpenLayers 的核心组件是 Map (来自 ol/Map 模块)。它会被渲染到一个目标容器中(例如包含地图的网页上的 div 元素)。所有地图属性都可以在构造时进行配置,也可以使用 setter 方法进行配置,例如 setTarget() 方法。
View The map is not responsible for things like center, zoom level and projection of the map. Instead, these are properties of a ol/View instance. Map 并不负责处理地图的中心点、缩放级别和投影等信息。这些是 ol/View 实例的属性。
import View from 'ol/View.js';
map.setView(new View({
center: ol.proj.fromLonLat([113.7064,23.29071]), // 地图中心点
zoom: 2,
}));Layer A layer is a visual representation of data from a source. OpenLayers has four basic types of layers:
ol.layer.Tile:用于显示基于图块的地图数据的图层,例如瓦片地图服务(TMS)或 Web Map Service(WMS)。ol.layer.Image:用于显示基于图像的地图数据的图层,例如基于图像的地图服务。ol.layer.Vector:用于显示矢量数据的图层,可以包含点、线、面等几何要素。ol.layer.Group:用于组合多个图层的图层组,可以同时管理多个子图层。ol.layer.Heatmap:用于显示热力图的图层,可以可视化密度数据。ol.layer.VectorTile:用于显示矢量切片的图层,可以提供高性能的矢量数据渲染。ol.layer.ImageStatic:用于显示静态图像的图层,可以显示单个静态图像。ol.layer.TileVector:用于显示矢量切片的图层,将矢量数据加载为图块以提供渲染效率。ol.layer.VectorImage:用于显示矢量数据的图层,使用矢量图像渲染引擎进行高性能渲染。ol.layer.TileLayer:用于显示图块数据的图层,与ol.layer.Tile类似,但是具有一些额外的功能。
Source Layer 的数据来源于 ol/source/source 及其子类; VectorSource 是矢量类型的 source, 往 Source 中添加 Feature 数据, 类似 geojson
ol.source.Vector:用于矢量数据的数据源,可以包含点、线、面等几何要素。ol.source.TileWMS:用于加载基于 Web Map Service(WMS)的图块数据的数据源。ol.source.ImageWMS:用于加载基于 WMS 的图像数据的数据源。ol.source.OSM:用于加载 OpenStreetMap 数据的数据源。ol.source.XYZ:用于加载 XYZ 格式的图块数据的数据源。ol.source.TileJSON:用于加载基于 TileJSON 规范的图块数据的数据源。ol.source.ImageStatic:用于加载静态图像的数据源。ol.source.GeoJSON:用于加载 GeoJSON 格式的矢量数据的数据源。ol.source.Cluster:用于对矢量数据进行聚类的数据源。ol.source.TileVector:用于加载矢量切片的数据源。
var source = new VectorSource();
const vector = new VectorLayer({
source: source,
style: {
'fill-color': 'rgba(255, 255, 255, 0.2)',
'stroke-color': '#ffcc33',
'stroke-width': 2,
'circle-radius': 7,
'circle-fill-color': '#ffcc33',
},
})
//线
let lineFeature = new Feature({
geometry: new LineString(
fromLonLats([
[ 113.70469925805553, 23.29105249899534 ],
[ 113.70475287618535, 23.290352873226524 ],
])
)
});
source.addFeature(lineFeature)
//点
const marker = new Point({
geometry: fromLonLat([ 113.70469925805553, 23.29105249899534 ]),
});
source.addFeature(marker)二次封装库
基于 openlayers 封装好的js库
React-OpenLayers:React-OpenLayers 是一个基于 OpenLayers 的 React 库,提供了一组 React 组件,使你可以方便地在 React 应用程序中使用 OpenLayers。它提供了针对地图、图层、要素和交互等的封装组件,简化了与 OpenLayers 的集成和操作。
Vue-OpenLayers:Vue-OpenLayers 是一个基于 OpenLayers 的 Vue 库,提供了一组 Vue 组件,用于在 Vue 应用程序中使用 OpenLayers。它提供了包括地图、图层、要素和交互等的封装组件,使你可以更轻松地在 Vue 项目中集成和使用 OpenLayers。
要素绘制
绘制点线面
//要素对象 点
const point = new ol.Feature({
geometry: new ol.geom.Point( ol.proj.fromLonLat([data.longitude, data.latitude ])),
type: 'draggable-point'
});
//要素对象 线
const lineFeature = new ol.Feature({
geometry: new ol.geom.LineString([ [[x, y], ... ] ]),
type: 'line'
});
//要素对象 面
const lonLatCoords = [
[ 113.5091696577307, 22.643547651857588 ],
[ 113.51008677217058, 22.64323079278843 ],
[ 113.50991514841571, 22.642606974359865 ],
...
]
const epsg3857Coords = lonLatCoords.map(coord => ol.proj.fromLonLat(coord));
const vectorSource = new ol.source.Vector()
const feature = new ol.Feature({
geometry: new ol.geom.Polygon([epsg3857Coords ])
});
//要素源
const vectorSource_machine = new ol.source.Vector();
vectorSource_machine.addFeature(point);
// 等价
//const vectorSource_machine = new ol.source.Vector({ features: [point] });
//图层 向量图层
const vectorLayer_machine = new ol.layer.Vector({
source: vectorSource_machine,
style: new ol.style.Style({//图层样式
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({color: 'red'}),
stroke: new ol.style.Stroke({
color: 'white', width: 2
})
}),
stroke: new ol.style.Stroke({
color: 'blue', width: 3
}),
fill: new ol.style.Fill({
color: 'rgba(0, 0, 255, 0.3)'
})
})
});
map.addLayer(vectorLayer_machine);点线面编辑
参考示例库: Draw and Modify Features
// 创建一个空的矢量图层用于显示路线
const source = new VectorSource();
const vector = new VectorLayer({
source: source,
style: {
'fill-color': 'rgba(255, 255, 255, 0.2)',
'stroke-color': '#ffcc33',
'stroke-width': 2,
'circle-radius': 7,
'circle-fill-color': '#ffcc33',
},
})
this.mapInstance.addLayer(vector)
// 创建一个绘制交互工具用于绘制路线
var draw = new Draw({
source: source,
type: 'LineString',// 指定绘制类型为线要素
});
this.mapInstance.addInteraction(draw);
// 创建一个修改交互工具用于编辑路线
var modify = new ol.interaction.Modify({
source: vectorLayer.getSource()
});
// 吸附 辅助编辑的
var snap = new Snap({source: source});
this.mapInstance.addInteraction(snap);确保 Feature 在地图范围内
// 获取要素的几何范围
var featureExtent = lineFeature.getGeometry().getExtent();
// 缩放地图以适应要素的几何范围
this.mapInstance.getView().fit(featureExtent, {
padding: [50, 50, 50, 50], // 可选:添加边缘留白
maxZoom: 18 // 可选:限制最大缩放级别
});
代码
import Map from "ol/Map";
import Overlay from "ol/Overlay"
import View from "ol/View";
import { LineString, Point } from "ol/geom";
import Feature from "ol/Feature";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import ImageLayer from 'ol/layer/Image.js';
import TileWMS from "ol/source/TileWMS.js";
import { Draw, Modify, Snap } from "ol/interaction";
import { OSM, XYZ, Vector as VectorSource, ImageStatic } from "ol/source";
import { Message } from "element-ui";
import { fromLonLat, toLonLat } from 'ol/proj'
const toLonLat = ol.proj.toLonLat
const fromLonLat = ol.proj.fromLonLat
function fromLonLats(lnglats){
var coordinates = [];
for (let index = 0; index < lnglats.length; index++) {
const element = lnglats[index];
coordinates.push(fromLonLat(element) );
}
return coordinates;
}
function toLonLats(coordinates){
var lnglats = [];
for (let index = 0; index < coordinates.length; index++) {
const element = coordinates[index];
lnglats.push(toLonLat(element) );
}
return lnglats;
}
export default {
props: {
//中心点 车的位置
lnglat: {
type: Object,
required: true,
},
defaultAction:{},
originalNode:{},
isEditAble:{}
},
components:{
nodeContent
},
data() {
return {
// isEditAble: true, //是否可编辑路径
mapInstance: null,
tdLayer: null,//天地图 图层
showNodes: [],
//任务线绘制
curTaskpInstance: null,
isStartTaskp: false, //是否开始任务中
recordLnglat: [], //记录任务的路径点
//实时位置
realMarkInstance: null,
//路径编辑
pathLayer: null,
drawInstance: null, //绘制
editorInstance: null, //修改
switchPathEditor: false,
currentPolyline: null,
//路段编辑
actionLayer: null,
actionPointLayer: null,
switchActionEditor: false,
actionPorintInstances: [],
curActionPolylineInstance: null,
pathActionData: {
//"0":{ "gears": 0, "sp_l": 0, "sp_r": 0 }
},
curActionDataIndex: 0,
curActionData: {
//{ "gears": 0, "sp_l": 0, "sp_r": 0 }
},
mousePosition: {},
sliderMarks: {
0: 0,
1: 1,
2: 2,
3: 3
},
//高精度图层
configLayers: [
{ _name:"广州-农装所", _layer: 'sny:gz_nzs_hp' , visible: true},
// { _name:"广州-白云-仲恺", _layer: 'sny:gz_by_zk', visible: true},
// { _name:"广州-室内-测试图层", _layer: 'sny:gz_test_sn' , visible: true},
{ _name:"广州-室内-蔬菜所测试室内外一体定位", _layer: 'sny:gz_scs_dp_sn' , visible: true},
],
};
},
created() {
if(!this.isEditAble){
this.isEditAble=false
// this.drawInstance.setActive(false)
}else{
this.isEditAble=true
}
this.curActionData = this.defaultAction;
// this.time = setInterval(() => {
// this.markerCar(this.lnglat.lng,this.lnglat.lat)
// },1000)
},
mounted() {
this.initAsnyc().then(res=>{
})
},
watch: {
lnglat(newVal,oldVal){
if(newVal){
this.lnglatUpdate();
this.markerCar(this.lnglat.lng,this.lnglat.lat)
}
}
//watch
// lnglat: function refresh() {
// console.log('okk111')
// this.lnglatUpdate();
// this.markerCar(this.lnglat.lng,this.lnglat.lat)
// },
// originalNode(newVal,oldVal){
// if(newVal){
// console.log(this.originalNode)
// if(this.originalNode.length>0){
// console.log('okkkkk')
// }
// }
// }
},
methods: {
initAsnyc(){
var self = this;
window._sny_map = self
////////////创建 初始化 地图
const promise = new Promise(function(resolve,reject){
self.$nextTick(()=>{
var dom = self.$refs.map_container;
var url = "http://t0.tianditu.gov.cn/img_w/wmts?" +
"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
"&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=c94ca7ff1fda359919b7132d1307ed62";
var layer = self.tdLayer = new TileLayer({
source: new XYZ({
url: url,
}),
name: "BaseMap",
})
var map = self.mapInstance = new Map({
target: dom,
layers: [layer],
controls:[],
view: self.initView(),
});
map.on('click', function(event) {
var pixel = event.pixel;
map.forEachFeatureAtPixel(pixel, function(feature, layer) {
var call_ = feature.get("onClick")
call_ && call_(event)
});
});
self.initConfigLayers()
self.createEditorInstance()
self.markerCar(self.lnglat.lng,self.lnglat.lat)
if(!self.isEditAble){
self.closePathEdit()}
resolve();
})
////////tick
});
return promise;
},
initView(){
var self = this;
var view = new View({
// center: fromLonLat([113.7064,23.29071]), // 地图中心点 数字土壤
// center: fromLonLat([113.337795, 23.146183]), //广州所
// center: fromLonLat([113.442650, 23.366700]), //仲恺
center: fromLonLat([self.lnglat.lng, self.lnglat.lat]),//根据上报点
zoom: 18, // 缩放级别
minZoom: 15,
maxZoom: 30,
constrainResolution: false
})
view.on("change:resolution", e=>{
let zval = e.target.getZoom()
if (zval > 18.35) {
self.tdLayer.setVisible(false)
}else{
self.tdLayer.setVisible(true)
}
})
return view;
},
initConfigLayers(){
for (let index = 0; index < this.configLayers.length; index++) {
const element = this.configLayers[index];
var wms = new TileLayer({
visible: element.visible,
source: new TileWMS({
url : "https://www.simae.cn/geoserver/ows",
// url : "http://192.168.40.112:8080/geoserver/ows",
crossOrigin: 'anonymous',
attributions: ' ',
params :{
layers: [element._layer],
Service: 'WMS',
Request: 'GetMap',
Version: '1.1.1',
format: 'image/png',
transparent: true,
},
serverType: 'mapserver',
}),
});
wms.set("id", element._layer)
this.mapInstance.addLayer(wms)
}
// this.mapCentreTo(113.337795, 23.146183)
// 'sny:gz_nzs_hp', 'sny:gz_by_zk', 'sny:gz_test_sn'
},
//修改配置图层后, 同步图层的可见性
syncConfigLayersVisible(){
var allLayers = this.mapInstance.getAllLayers()
for (let index = 0; index < this.configLayers.length; index++) {
const element = this.configLayers[index];
const layerInstance = allLayers.find(e=>e.get("id") === element._layer);
layerInstance.setVisible(element.visible)
}
},
mapCentreTo(lng, lat){
let v = this.mapInstance.getView()
v.setCenter(fromLonLat([lng, lat]))
},
//开始任务 实时路径
startRenderPath(){
this.isStartTaskp = true;
},
//暂停任务 实时路径
pauseRenderPath(){
this.isStartTaskp = false;
},
//开始暂停的任务 实时路径
pauseStartRenderPath(){
this.isStartTaskp = true;
},
//任务结束 实时路径
endRenderPath(){
this.isStartTaskp = false;
this.recordLnglat = [];
this.realPathLayer.getSource().clear();
},
lnglatUpdate() {
if (this.isStartTaskp) {
//开始记录
var lnglats = [this.lnglat.lng, this.lnglat.lat]
this.recordLnglat.push(lnglats);
//画出来 实时线
this.renderRealPath()//渲染
}
//更新实时位置
},
//初始化 车的位置标记
markerCar(lng,lat) {
if(this.marker){
this.mapInstance.removeOverlay(this.marker)
}
let lnglats =fromLonLat([lng,lat])
let img = document.createElement("img")
img.src = require('/src/assets/images/mark.png')//图片地址
this.marker = new Overlay({
position: lnglats,
positioning: 'center-center',
element: img,
stopEvent:false
});
this.mapInstance.addOverlay(this.marker);
},
updateMarkerCar() {
var self = this;
if (self.realMarkInstance) {
let lnglats = wgs84togcj02(this.lnglat.lng, this.lnglat.lat);
self.realMarkInstance.moveTo(lnglats, { duration: duration });
}
},
//实时路径 渲染
renderRealPath(){
var source = this.realPathLayer.getSource()
// if (source.getFeatures().length > 0){
// let lineFeature = source.getFeatures()
// }else{
// }
let path = this.recordLnglat;
let lineFeature = new Feature({
geometry: new LineString(
fromLonLats(path)
)
});
source.clear()
source.addFeature(lineFeature)
},
createEditorInstance() {
var self = this;
///////////// 实时 运行路径
const rvector = new VectorLayer({//规划路线颜色
source: new VectorSource(),
style: {
'fill-color': 'rgba(255, 255, 255, 0.2)',
'stroke-color': '#FF0000',
'stroke-width': 2,
'circle-radius': 7,
'circle-fill-color': '#FF0000',
},
})
this.realPathLayer = rvector
this.mapInstance.addLayer(rvector)
///////////////////// 路径规划
var source = new VectorSource();
const vector = new VectorLayer({
//规划路线颜色
source: source,
style: {
"fill-color": "rgba(255, 255, 255, 0.2)",
"stroke-color": "#ffcc33",
"stroke-width": 2,
"circle-radius": 7,
"circle-fill-color": "#ffcc33",
},
});
this.pathLayer = vector;
this.mapInstance.addLayer(vector);
const actionLayer = new VectorLayer({
//路段修改 线
source: new VectorSource(),
style: {
"fill-color": "rgba(255,255,255,0.2)",
"stroke-color": "#00ff00",
"stroke-width": 2,
"circle-radius": 7,
"circle-fill-color": "#00ff00",
},
});
this.actionLayer = actionLayer;
this.mapInstance.addLayer(actionLayer);
const actionPointLayer = new VectorLayer({
//路段修改 点
source: new VectorSource(),
style: {
"fill-color": "rgba(255,255,255,0.2)",
"stroke-color": "#00ff00",
"stroke-width": 2,
"circle-radius": 8,
"circle-fill-color": "#00ff00",
},
});
this.actionPointLayer = actionPointLayer;
this.mapInstance.addLayer(actionPointLayer);
//绘制对象
var draw = new Draw({
source: source,
type: "LineString",
});
this.drawInstance = draw;
draw.on("drawend", (event) => {
setTimeout(() => {
draw.setActive(false);
self.$emit("drawend")
self.switchActionEdit()
}, 50);
});
this.mapInstance.addInteraction(draw);
//修改对象
const modify = new Modify({
source: source,
type: "LineString",
});
this.editorInstance = modify;
this.mapInstance.addInteraction(modify);
//
var snap = new Snap({ source: source });
this.mapInstance.addInteraction(snap);
},
switchPathEdit() {
this.switchPathEditor = !this.switchPathEditor;
if (this.switchPathEditor) {
this.openPathEdit();
} else {
this.closePathEdit();
}
},
openPathEdit() {
this.switchPathEditor = true;
this.editorInstance.setActive(true);
if (this.pathLayer.getSource().getFeatures().length > 0) {
this.drawInstance.setActive(false);
} else {
this.drawInstance.setActive(true);
}
},
closePathEdit() {
this.switchPathEditor = false;
this.drawInstance.setActive(false); //禁用 绘制和编辑
this.editorInstance.setActive(false);
},
cleanEdit() {
//清除全部
this.closePathEdit();
this.actionLayer.getSource().clear();
this.pathLayer.getSource().clear();
/**********************************
let lineFeature = new Feature({
geometry: new LineString(
fromLonLats([
[ 113.70469925805553, 23.29105249899534 ],
[ 113.70475287618535, 23.290352873226524 ],
[ 113.70506922315131, 23.291047572066148 ],
[ 113.70514428853305, 23.290397215813968 ],
[ 113.70546599731199, 23.291037718207264 ],
[ 113.70586346424653, 23.290745285031775 ],
[ 113.7063567510409, 23.290577768950797 ]
])
)
});
let pointFeature = new Feature({
geometry: new Point(fromLonLat( [ 113.70475287618535, 23.290352873226524 ]))
});
this.actionLayer.getSource().clear()
// this.actionLayer.getSource().addFeature(lineFeature)
this.actionPointLayer.getSource().addFeature(pointFeature)
*******************************/
},
///////////////////// 路段编辑
switchActionEdit() {
this.switchActionEditor = !this.switchActionEditor;
if (this.switchActionEditor) {
this.openActionEdit();
} else {
this.closeActionEdit();
}
},
openActionEdit() {
if (this.actionPorintInstances.length > 0) {
//已编辑中
return;
}
var elements = this.pathLayer.getSource().getFeatures();
if (elements.length <= 0) {
Message({ message: "未绘制路径", type: "error", duration: 5 * 1000 });
return;
}
this.closePathEdit(); //先禁用 路径编辑
let self = this;
var geometry = elements[0].getGeometry();
var coordinates = geometry.getCoordinates();
let paths = toLonLats(coordinates);
this.actionPointLayer.getSource().clear();
for (let index = 0; index < paths.length; index++) {
const lnglat = paths[index];
//路段编辑时的 点
const pointFeature = new Feature({
geometry: new Point(fromLonLat(lnglat)),
});
pointFeature.set("onClick", (e) => {
self.renderActionEditPath(index);
});
this.actionPointLayer.getSource().addFeature(pointFeature);
this.pathActionData[index + ""] = JSON.parse(
JSON.stringify(this.defaultAction)
);
this.actionPorintInstances.push(pointFeature);
}
// this.mapInstance.addOverLay(this.actionPorintInstances);
self.renderActionEditPath(0);
this.switchActionEditor = true;
},
closeActionEdit() {
// this.mapInstance.removeOverLay(this.actionPorintInstances);
// if(this.curActionPolylineInstance)
// this.mapInstance.removeOverLay(this.curActionPolylineInstance);
this.actionLayer.getSource().clear();
this.actionPointLayer.getSource().clear();
this.actionPorintInstances = [];
this.switchActionEditor = false;
},
//渲染某一段
renderActionEditPath(index) {
var elements = this.pathLayer.getSource().getFeatures();
var geometry = elements[0].getGeometry();
var coordinates = geometry.getCoordinates();
let paths = toLonLats(coordinates);
if (index < 0 || index + 1 >= paths.length) {
return;
}
let path = [paths[index], paths[index + 1]];
this.curActionData = this.pathActionData[index + ""];
this.curActionDataIndex = index;
let lineFeature = new Feature({
geometry: new LineString(fromLonLats(path)),
});
this.actionLayer.getSource().clear();
this.actionLayer.getSource().addFeature(lineFeature);
},
getAsNodes() {
var elements = this.pathLayer.getSource().getFeatures();
if (elements.length <= 0) {
Message({ message: "未绘制路径", type: "error", duration: 5 * 1000 });
return;
}
var geometry = elements[0].getGeometry();
var coordinates = geometry.getCoordinates();
let paths = toLonLats(coordinates);
let ret = this.encodeAsNodes(paths);
return ret;
},
encodeAsNodes(paths) {
let nodes = new Array(paths.length);
for (let index = 0; index < paths.length; index++) {
const element = paths[index];
nodes[index] = {
lng: element[0],
lat: element[1],
action: element.action,
};
//合路径数据
nodes[index].action = this.pathActionData[index + ""]
? this.pathActionData[index + ""]
: this.defaultAction;
}
return nodes;
},
complete(){
// console.log(this.pathActionData)
// this.closeActionEdit()
var node=this.getAsNodes()
var nodes= JSON.parse(JSON.stringify(node).replace(/lng/g,"longitude").replace(/lat/g, "latitude"));
this.$emit('complete',nodes)
},
initPath(nodes){
//加载, 加载后编辑路段, 不能编辑线!
this.setByNodes(nodes)
},
setByNodes(nodes){
var source = this.pathLayer.getSource();
//geo
var lonlats = nodes.map(e=>[e.lng, e.lat])
let lineFeature = new Feature({
geometry: new LineString(
fromLonLats(lonlats)
)
});
source.addFeature(lineFeature)
//action
for (let index = 0; index < nodes.length; index++) {
const element = nodes[index];
this.pathActionData[index+''] = element.action?element.action: this.defaultAction
}
},
},
};
比例尺
/* 比例尺自定义样式 */
.custom-scale-line-inner {
position: absolute;
bottom: 10px;
left: 20px;
text-align: center;
border: 2px solid hsl(113, 81%, 58%);
color: #000000;
font-size: 14px;
font-weight: bold;
padding: 2px 4px;
} // 添加线条比例尺
const scaleLine = new ol.control.ScaleLine({
units: 'metric', // 使用公制单位
bar: false, // 线条样式(非条形)
steps: 4, // 4个刻度
text: true, // 显示文字
className: 'custom-scale-line' // 使用自定义样式类
});
map.addControl(scaleLine);Geojson 编辑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>openlayers </title>
<style type="text/css">
#mapDiv{
/*地图(容器)显示大小*/
width: 1200px;
height: 800px;
}
</style>
</head>
<body>
<div style="display: flex;">
<div id="mapDiv"></div>
<div style="width:188px;">
<div id="tip">==============</div>
<button onclick="document.getElementById('fileInput').click()">加载GeoJSON文件</button>
<input type="file" id="fileInput" accept=".geojson, .json" onchange="loadGeoJSONFile(event)">
<button onclick="toggleInteraction('draw-point')">绘制点</button>
<button onclick="toggleInteraction('draw-line')">绘制线</button>
<button onclick="toggleInteraction('draw-polygon')">绘制面</button>
<button onclick="toggleInteraction('modify')">修改要素</button>
<button onclick="toggleInteraction('delete')">删除要素</button>
<button onclick="saveGeoJSON()">保存GeoJSON</button>
</div>
<textarea id="output" readonly></textarea>
</div>
<script crossorigin="anonymous" src="https://lib.baomitu.com/openlayers/7.4.0/dist/ol.js"></script>
<!-- <script src="http://api.tianditu.gov.cn/api?v=4.0&tk=c94ca7ff1fda359919b7132d1307ed62" type="text/javascript"></script> -->
<script type="text/javascript">
const toLonLat = ol.proj.toLonLat
const fromLonLat = ol.proj.fromLonLat
function fromLonLats(lnglats){
var coordinates = [];
for (let index = 0; index < lnglats.length; index++) {
const element = lnglats[index];
coordinates.push(fromLonLat(element) );
}
return coordinates;
}
function toLonLats(coordinates){
var lnglats = [];
for (let index = 0; index < coordinates.length; index++) {
const element = coordinates[index];
lnglats.push(toLonLat(element) );
}
return lnglats;
}
///////////////////////////////////////////////////////////////////////////
var map;
let drawInteraction, modifyInteraction, selectInteraction;
const vectorSource = new ol.source.Vector();
function initMap (){
//天地图 卫星图 WMTS xyz
var url = "http://t0.tianditu.gov.cn/img_w/wmts?" +
"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
"&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=c94ca7ff1fda359919b7132d1307ed62";
var baselayer =new ol.layer.Tile({
source: new ol.source.XYZ({
url: url,
}),
name: "BaseMap",
})
////////////////// 本地 天地图 瓦片
const xyzSource = new ol.source.XYZ({
crossOrigin: 'anonymous', tileUrlFunction: (zxy) => {
const [z, x, y] = zxy;
const yn = Math.pow(2, z) - y - 1;
//瓦片路径地址为: map/{id}/{format.replaceAll("{x}",x).replaceAll("{y}",yn).replaceAll("{z}",z)}
return `./440106000000/${z}/${x}/${y}/tile.jpg`
}
})
const offlineMapLayer = new ol.layer.Tile({
zIndex: 3,
source: xyzSource
});
//////////
var center = [115.01255129,22.8189894]
center = [115.01236630, 22.81909524]
center = [113.337668, 23.145967]//广州市天河区卫星图
center = [113.33203412782674,23.183219636891778]//广州市天河区 边缘
const map = window.map = new ol.Map({
target: 'mapDiv',
layers: [
baselayer
],
view: new ol.View({
center: ol.proj.fromLonLat(center),
zoom: 18, // 缩放级别
minZoom: 0,
maxZoom: 30,
constrainResolution: true
}),
});
// map.addLayer(offlineMapLayer);
// map.addLayer(
// new ol.layer.Tile({ source: new ol.source.OSM() })
// )
map.addLayer(
new ol.layer.Vector({
source: vectorSource,
style: function(feature) {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({ color: 'red' }),
stroke: new ol.style.Stroke({ color: 'white', width: 2 })
}),
stroke: new ol.style.Stroke({
color: 'blue',
width: 2
})
});
}
})
)
}
/////////////////// 编辑要素
// 统一样式设置
function getDefaultStyle() {
return new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({ color: 'red' }),
stroke: new ol.style.Stroke({ color: 'white', width: 2 })
}),
stroke: new ol.style.Stroke({
color: 'blue',
width: 2
}),
fill: new ol.style.Fill({
color: 'rgba(0, 255, 0, 0.3)'
})
});
}
// 加载GeoJSON数据
function loadGeoJSON(geojson) {
const features = new ol.format.GeoJSON().readFeatures(geojson, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
vectorSource.addFeatures(features);
map.getView().fit(vectorSource.getExtent());
}
// 切换交互模式
function toggleInteraction(type) {
clearInteractions();
switch (type) {
case 'draw-point':
drawInteraction = new ol.interaction.Draw({
source: vectorSource,
type: 'Point'
});
map.addInteraction(drawInteraction);
break;
case 'draw-line':
drawInteraction = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString'
});
map.addInteraction(drawInteraction);
break;
case 'draw-polygon':
drawInteraction = new ol.interaction.Draw({
source: vectorSource,
type: 'Polygon'
});
map.addInteraction(drawInteraction);
break;
case 'modify':
modifyInteraction = new ol.interaction.Modify({
source: vectorSource
});
map.addInteraction(modifyInteraction);
break;
case 'delete':
selectInteraction = new ol.interaction.Select();
map.addInteraction(selectInteraction);
selectInteraction.on('select', (e) => {
e.selected.forEach(feature => vectorSource.removeFeature(feature));
});
break;
}
}
// 清除所有交互
function clearInteractions() {
if (drawInteraction) map.removeInteraction(drawInteraction);
if (modifyInteraction) map.removeInteraction(modifyInteraction);
if (selectInteraction) map.removeInteraction(selectInteraction);
}
// 保存为GeoJSON
function saveGeoJSON() {
const geojsonFormat = new ol.format.GeoJSON();
const features = vectorSource.getFeatures();
const geojson = geojsonFormat.writeFeatures(features, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
console.log('当前GeoJSON数据:', geojson);
// 自动下载文件
const blob = new Blob([geojson], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'edited-data.geojson';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// //////////////////////// 加载本地GeoJSON文件
function loadGeoJSONFile(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const geojson = JSON.parse(e.target.result);
clearMapData(); // 清除现有数据
loadGeoJSON(geojson);
} catch (error) {
alert('文件解析失败: ' + error.message);
}
};
reader.readAsText(file);
}
// 清空地图数据
function clearMapData() {
vectorSource.clear();
}
// 加载GeoJSON对象到地图
function loadGeoJSON(geojson) {
try {
const features = new ol.format.GeoJSON().readFeatures(geojson, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857'
});
vectorSource.addFeatures(features);
map.getView().fit(vectorSource.getExtent());
} catch (error) {
alert('GeoJSON加载失败: ' + error.message);
}
}
// 初始化
initMap();
// 绑定事件
var clickHandler = function (event) {
var coordinate = event.coordinate;
var lonLat = ol.proj.toLonLat(coordinate);
document.getElementById("tip").textContent = '坐标:[' +lonLat + ']'
};
map.on('click', clickHandler);
//mapCentreTo
function mapCentreTo(lng, lat){
let v = map.getView()
v.setCenter(fromLonLat([lng, lat]))
}
</script>
</body>
</html>Leaflet
Leaflet 是一个为建设移动设备友好的互动地图,而开发的现代的、开源的 JavaScript 库. 它是由 Vladimir Agafonkin 带领一个专业贡献者团队开发,虽然代码仅有 38 KB,但它具有开发人员开发在线地图的大部分功能.
leaflet 可以通过简单的 Api 快速构建出简单的地图,结合其他的接口(Marker、 Popup、Icon、Polyline、Polygon等)即可快速实现点、线、面的绘制,社区中也有非常丰富的插件,可以低成本的实现诸如热力图、插值、聚合、数据可视化等功能,需要注意一点 leaflet 只能实现 2D 地图.
Cesium JS
https://www.cesium.com/ https://github.com/CesiumGS/cesium API - 文档 示例 - sandcastle.cesium
CesiumJS是一个JavaScript库,用于在没有插件的情况下在web浏览器中创建3D地球仪和2D地图。它使用WebGL进行硬件加速图形,并且是跨平台、跨浏览器的,并针对动态数据可视化进行了调整。 https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/ https://www.cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/
关键对象概念
- Viewer:
Cesium.Viewer是CesiumJS的入口点,用于创建和控制整个场景。它提供了许多配置选项,包括地形、影像、相机控制等。
- Camera:
Cesium.Camera用于控制视 角和场景中的相机位置。它可以用来飞行、旋转、缩放等。
- Scene:
Cesium.Scene是CesiumJS的核心对象,代表整个3D场景。它包含了所有的图形资源,如几何体、材质、光源等。Cesium.Globe它代表了一个三维地球模型。支持用户与地球交互,如缩放、旋转、倾斜视图等。允许开发者自定义地球的外观,包括颜色、水面效果、大气效果等。
- Entity:
Cesium.Entity是一个高层次的、易于使用的对象,用于表示场景中的实体。实体可以包含位置、外观、时间动态等属性。
- DataSource:
Cesium.DataSource和其子类用于加载和管理数据源,例如CzmlDataSource、GeoJsonDataSource等,这些数据源可以包含多个实体。
- ImageryProvider:
Cesium.ImageryProvider是一个抽象类,用于提供地图影像。CesiumJS提供了多种实现,如WebMapServiceImageryProvider、WebMapTileServiceImageryProvider等。
- TerrainProvider:
Cesium.TerrainProvider用于提供地形数据。CesiumJS支持多种地形数据源,如CesiumTerrainProvider、EllipsoidTerrainProvider等。
quickstart (for vite)
https://github.com/CesiumGS/cesium-webpack-example#cesium-webpack-example
依赖
npm create vite@latest hello-cesiumjs -- --template vue
### 安装cesium和vite-plugin-cesium
# If you’re building your application using a module bundler such as Webpack, Parcel, or Rollup, you can install CesiumJS by running:
npm install cesium
npm install --save-dev @types/cesium
# 不装vite-plugin-cesium可能会导致样式问
npm install vite-plugin-cesium
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cesium from 'vite-plugin-cesium'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), cesium()],
})
Cesium + 天地图
登陆账号拿 Cesium tokent https://ion.cesium.com/tokens?page=1
<template>
<div ref="container" id="container" style="width:100%;height:100%;" >
</div>
</template>
<script setup lang="ts" >
import { ref, reactive, onMounted } from "vue";
import * as Cesium from 'cesium';
import { Cartesian3, createOsmBuildingsAsync, Ion, Math as CesiumMath, Terrain, Viewer } from 'cesium';
import "cesium/Build/Cesium/Widgets/widgets.css";
// Cesium tokent
Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxxxxxx'
onMounted(() => {
var container = ref(null)
// Initialize the Cesium Viewer in the HTML element
const viewer = new Cesium.Viewer("container", {
terrain: Cesium.Terrain.fromWorldTerrain(),//地形
});
// 创建天地图的 ImageryProvider (可以理解为地形贴图)
viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
url:
"http://t0.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img"
+"&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}"
+"&style=default&format=tiles&tk=天地图tk",
layer: "tdtBasicLayer",
style: "default",
format: "image/jpeg",
tileMatrixSetID: "GoogleMapsCompatible",
})
)
// fly to 中国
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 17850000),
orientation: {
heading: Cesium.Math.toRadians(348.4202942851978),
pitch: Cesium.Math.toRadians(-89.74026687972041),
roll: Cesium.Math.toRadians(10),
}
});
})
</script>
<style>
</style>WMS 加载
var wmslayer = new Cesium.WebMapServiceImageryProvider({
url:"http://192.168.50.191:8080/geoserver/wms",
layers:'sny:tdlylx_2022_all',
parameters: {
transparent: true,
format: "image/png",
styles: "default",
},
});
viewer.imageryLayers.addImageryProvider(wmslayer)实例
坐标转换
经纬度到3D坐标
const position = Cesium.Cartesian3.fromDegrees( -113.0744619,23.0503706, 50);摄像头
//跳转位置
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(115.0, 23.0, 10000),
});// // fly to 中国
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 17850000),
orientation: {
heading: Cesium.Math.toRadians(348.4202942851978),
pitch: Cesium.Math.toRadians(-89.74026687972041),
roll: Cesium.Math.toRadians(10),
}
});地形裁剪
ClippingPlaneCollection 是 CesiumJS 中用于定义和管理一组剪裁平面的集合。你可以使用它来对模型、地形或 3D Tiles 进行裁剪。
var scene = viewer.scene;
var globe = scene.globe;
// 定义裁剪平面集合,用于裁剪地形
var clippingPlanes = new Cesium.ClippingPlaneCollection({
planes : [
new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), -0.001), // 裁剪区域的西侧平面
new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), -0.001), // 裁剪区域的东侧平面
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), -0.001), // 裁剪区域的南侧平面
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, -1.0, 0.0), -0.001), // 裁剪区域的北侧平面
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 0.0, 1.0), -50.0) // 裁剪区域的底部平面
],
edgeWidth: 1.0,
edgeColor: Cesium.Color.WHITE
});
// 应用裁剪平面到地球对象
globe.clippingPlanes = clippingPlanes;
// 设置裁剪区域的位置
var centerLongitude = 113.0;
var centerLatitude = 23.0;
var height = 50.0; // 高度,以米为单位
// 创建平移矩阵,将裁剪平面移动到指定位置
var translation = Cesium.Cartesian3.fromDegrees(centerLongitude, centerLatitude, height / 2);
clippingPlanes.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(translation);
// 设置视图,以便查看裁剪效果
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(centerLongitude, centerLatitude, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-30.0),
roll: 0.0
}
});面模型
// 定义多边形的坐标
var positions = Cesium.Cartesian3.fromDegreesArray([
116.391275, 39.90765, // 西南角
116.414275, 39.90765, // 西北角
116.414275, 39.93265, // 东北角
116.391275, 39.93265 // 东南角
]);
// 创建一个多边形实体
var polygon = viewer.entities.add({
polygon: {
hierarchy: positions,
height: 1000, // 高度
material: Cesium.Color.RED.withAlpha(0.5) // 透明材质
}
});
// 跳转到多边形的位置
viewer.zoomTo(viewer.entities);监听点击事件 经纬度
// 添加点击事件监听器
viewer.canvas.addEventListener('click', function (event) {
// 获取点击位置的笛卡尔坐标(世界坐标)
var cartesian = viewer.camera.pickEllipsoid(
new Cesium.Cartesian3(event.clientX, event.clientY),
viewer.scene.globe.ellipsoid
);
// 如果我们成功获取了位置,则转换为经纬度
if (cartesian) {
var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
var longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(2);
var latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(2);
console.log('Longitude: ' + longitude + ', Latitude: ' + latitude);
} else {
console.log('未选中地球表面');
}
}, false);3dTiles
obj23dtiles
Cesium 官方提供了一个名为 obj23dtiles 的工具,可以将 OBJ 模型转换为 3D Tiles。
npm install -g obj23dtiles
obj23dtiles --tileset -i BlockABA_c005.obj
obj23dtiles --tileset -i BlockABA_c005_90.obj
加载 3dTiles
<!--
* @Author: yangfh
* @Date: 2025-07-24 11
* @LastEditors: yangfh
* @LastEditTime: 2025-07-28 10
* @Description:
*
-->
<template>
<div ref="container" id="cesiumContainer" style="width:100%;height:100%;" >
</div>
</template>
<script setup >
import { ref, reactive, onMounted } from "vue";
import * as Cesium from 'cesium';
import { Cartesian3, createOsmBuildingsAsync, Ion, Math as CesiumMath, Terrain, Viewer } from 'cesium';
import "cesium/Build/Cesium/Widgets/widgets.css";
// Cesium tokent
Ion.defaultAccessToken = '..KA-Bo_6D7FsKd_nvie7notXhWDlGD5i0zFzjxuMQFeI'
onMounted( async () => {
var container = ref(null)
const viewer = new Cesium.Viewer("cesiumContainer", {
});
try {
const classificationTilesetUrl = "http://192.168.20.130/3d/outputTiles/tileset.json";
const classificationTileset = await Cesium.Cesium3DTileset.fromUrl(
classificationTilesetUrl,
/** // 添加调试参数, 注意 classificationType 设置的不对, 将不会显示模型, **/
{
debugShowBoundingVolume: true, // 显示包围盒
debugShowContentBoundingVolume: true,
debugShowViewerRequestVolume: true
},
);
classificationTileset.style = new Cesium.Cesium3DTileStyle({
color: "rgba(255, 0, 0, 0.9)", // 提高不透明度
show: true
});
viewer.scene.primitives.add(classificationTileset);
classificationTileset.tileFailed.addEventListener((tile, error) => {
console.error(`加载错误 Tile failed to load: ${tile.content.url}`);
console.error(`加载错误 Error: ${error}`);
});
// 定位到瓦片
viewer.zoomTo(classificationTileset)
.catch(e => {
console.log("自动缩放失败,尝试备用位置");
// 如果自动缩放失败,使用transform中的位置
const position = Cesium.Cartesian3.fromElements(
1215031.1400967704, -4736384.220698239, 4081666.9662339143
);
viewer.camera.setView({
destination: position,
orientation: {
heading: 0,
pitch: -Math.PI/4,
roll: 0
}
});
});
} catch (error) {
console.error(`加载错误 Error loading tileset: ${error}`);
}
// 添加调试功能
viewer.scene.debugShowFramesPerSecond = true;
viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin);
var scene = viewer.scene;
window._scene = scene
// 添加点击事件监听器
viewer.canvas.addEventListener('click', function (event) {
// 获取点击位置的笛卡尔坐标(世界坐标)
var cartesian = viewer.camera.pickEllipsoid(
new Cesium.Cartesian3(event.clientX, event.clientY),
viewer.scene.globe.ellipsoid
);
if (cartesian) {
var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
var longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(2);
var latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(2);
console.log('点击位置 Longitude: ' + longitude + ', Latitude: ' + latitude);
} else {
console.log('未选中地球表面');
}
}, false);
})
</script>
<style>
</style>Mapbox
Mapbox GL JS 是一个 JavaScript 库,它使用 WebGL,以 vector tiles 和 Mapbox styles 为来源,将它们渲染成互动式地图. 它是 Mapbox GL 生态系统的一部分,其中还包括 Mapbox Mobile,它是一个用 C++ 编写的兼容桌面和移动平台的渲染引擎.
高德地图
WMTS加载
<Exception exceptionCode="TileOutOfRange" locator="TILECOLUMN">
XYZ 加载 (EPSG:3857)
https://lbs.amap.com/demo/jsapi-v2/example/thirdlayer/custom-grid-map
const layer = 'sny:ss_zhnysfjd_one_tree_one_code_0_03'
var xyzTileLayer = new AMap.TileLayer({
getTileUrl:function (x, y, z){
var url = "http://192.168.40.112:8080/geoserver/gwc/service/wmts?"
+"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.1.1&LAYER="+layer
+"&STYLE=&TILEMATRIXSET=EPSG:3857&FORMAT=image/png"
+`&TILEMATRIX=EPSG:3857:${z}&TILEROW=${y}&TILECOL=${x}`;
return url;
},
zIndex: 100
});
this.map.add(xyzTileLayer)点击获取最近的路径点
document.getElementById('#map').addEventListener('mousemove', (e) => {
var pixel = new AMap.Pixel(e.offsetX, e.offsetY)
var lnglat = map.containerToLngLat(pixel)
//首先拿到 鼠标的经纬度 mousePosition
self.mousePosition = lnglat
})polyline.on('click', function(ev) {
let path = ev.target.getPath();
let minPoint = path[0];
//循环找到 最小距离的路径点
path.forEach((item, index) => {
var distance = AMap.GeometryUtil.distance(item, self.mousePosition);
var minSistance = AMap.GeometryUtil.distance(minPoint, self.mousePosition);
if (distance < minSistance) {
minPoint = item;
minIndex = index;
}
})
console.log('编辑点索引: ',minIndex);
})判断几何是否重叠
/**
* @description: 比较路径是否重叠;
*/
isOverlap(data){
let len = data.length
let inlen = data.length - 1
for (let index = 1; index < len; index++) {
const p1 = data[index-1];
const p2 = data[index];
const baseLine = [p1,p2];
const inForech = index+1;
//
for (let j = inForech; j < inlen; j++) {
const ip1 = data[j];
const ip2 = data[j+1];
if(j+1 - (index-1) === inlen){// 末尾相连的
break;
}
const another = [ip1 ,ip2];
//let __debug__test = index-1 +","+ index +" <===> "+j +", "+(j+1);
////- console.log(__debug__test+"; loop");
var isInSide = AMap.GeometryUtil.doesLineLineIntersect(baseLine, another)
if (isInSide) {
////- console.log(__debug__test+"; 存在重叠!");
return true;;
}
}
}
return false;
}踩坑
自定义风格 无效?
(()=>{
const AMAP_WEB_JS_KEY = 'dc9e0eaa3938f485e044808ec824cc6c'
const AMAP_SECURITY_JS_CODE = 'd00957714b68cff7f4b9d935f02c02e5'
var center = [114.015492,22.949568];
var mapDom = document.getElementById("app")
window._AMapSecurityConfig = {
securityJsCode: AMAP_SECURITY_JS_CODE
}
AMapLoader.load({
key: AMAP_WEB_JS_KEY,
version:"2.0",
}).then((AMap)=>{
let map = self.map = new AMap.Map(mapDom, {
zoom: 13,
//layers:[new AMap.TileLayer()],
mapStyle:'amap://styles/blue',
zooms: [0, 18],
viewMode: '2D',
center: center
});
window._map = map
console.log("--------------完成---, ", map)
}).catch(e=>{
console.log("--------------catch---, ",e)
})
})()
与 mockjs 冲突, f**K
import { mockXHR } from "@/mock/index";
mockXHR()天地图
WMS 加载
const layer = 'sny:ss_zhnysfjd_180_planning'
var config = {
version: "1.1.1", //请求服务的版本
layers: layer,
transparent: true, //输出图像背景是否透明
styles: "", //每个请求图层的用","分隔的描述样式
format: "image/png" //输出图像的类型
};
var url = "http://192.168.40.112:8080/geoserver/ows"
var wmsLayer = new T.TileLayer.WMS(url, config);
this.map.addLayer(wmsLayer); WMTS 加载
XYZ方式
/// WMTS XYZ
var imageURL = "http://192.168.40.112:8080/geoserver/gwc/service/wmts?"
+"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.1.1&LAYER="+layer
+"&STYLE=&TILEMATRIXSET=EPSG:3857&FORMAT=image/png"
+`&TILEMATRIX=EPSG:3857:{z}&TILEROW={y}&TILECOL={x}`;
var lay = new T.TileLayer(imageURL, {minZoom: 1, maxZoom: 30});
this.map.addLayer(lay);超出范围会导致GeoServer响应异常XML, 导出天地图渲染空白网格. 可参考 http://lbs.tianditu.gov.cn/api/js4.0/class.html 图层类 → TileLayerOptions 的 bounds 属性设置指定范围内显示瓦片
| 属性 | 类型 | 默认值 | 说明 |
| minZoom | number | 0 | 此图层的最低缩放级别。 |
| maxZoom | number | 18 | 此图层的最高缩放级别。 |
| errorTileUrl | string | "" | 当没有瓦片时所显示的错误图片地址。 |
| opacity | number | 1.0 | 设置图层的透明度(0.0-1.0)。默认值为 1.0不透明。 |
| zIndex | number | null | 图层的显示顺序。 |
| bounds | LngLatBounds | null | 设置指定范围内显示瓦片。 |