常见问题
图形透明的地方不可交互
maptalks 体系内图形透明的地方不可交互(鼠标点击等),体现在:
- 虚线
- 图片透明的区域
- 透明的区域面等
为什么这样呢?
- 透明的地方可以交互不符合人的直觉
- 图标透明的地方可以交互会带来其他的一系列问题:
比如这样的图片
如果可以交互,会存在当用户鼠标 进入图片的p2处就可以交互了,导致从视觉上违反人的直觉
如果有两个图片存在遮盖关系,那问题将更加严重,当鼠标处于p2处时,可能下面有一个图标,视觉上鼠标在 下面的绿色图标内,但是结果响应交互的是上面的这个红色图标,用户会彻底懵逼
- 有的同学可能说可以为每个图形设置一个配置项来决定透明的地方是否可交互?可以的,但是这样交互性能会下降很多, 目前maptalks内判断当前坐标位置处是否有像素值,有才走图形交互逻辑,否则不走,如果透明可交互意味地图任何位置 都可以交互,那么也就意味着性能下降一半,而且仍然存在图片遮盖问题
目前maptalks选择这种方式也是也是个折中的方法,因为没有找到好的完美的方法,当然这种方案缺点也很明显, 就是如上图中p1处不可交互了
所以在在设计图标时,可以做好透明度控制,需要交互的地方不要将透明度设置为0,可以设置为一个非常小的值,肉眼 不可见但是计算机可见(比如透明度0.01)这种,以上图为例,可以把p1处的透明度设置为很小的值,p2处完全透明
图形处于编辑时无法对图形进行操作
当图形处于编辑时,图形处于冻结状态,这时你无法对图形进行坐标更改,样式更改,旋转 等操作的
- 要么在图形编辑前进行这些操作
- 要么这时关闭编辑在进行这些操作
绘制跨日期线的图形
图形上有个配置选项antiMeridian
开启即可,注意你的数据必须是跨日期线的才可以,否则 仅仅配置了仍然不生效的
WARNING
该特性要求 maptalks 版本>1.0.0-rc.33
const layer = new maptalks.VectorLayer("layer").addTo(map);
function getGeoOptions() {
return {
antiMeridian: true,
};
}
const polygon = new maptalks.Polygon(
[
[
[171.2109375, 70.50582938421903],
[-153.28125, 67.75439373171932],
[-154.3359375, 57.72055958184691],
[172.265625, 53.3492177615793],
],
],
{
antiMeridian: true,
},
).addTo(layer);
const line = new maptalks.LineString(
[
[171.2109375, 70.50582938421903],
[-153.28125, 70.50582938421903],
],
{
antiMeridian: true,
arrowStyle: "classic",
},
).addTo(layer);
const line1 = new maptalks.LineString(
[
[-153.28125, 70.50582938421903],
[171.2109375, 70.50582938421903],
],
{
antiMeridian: true,
arrowStyle: "classic",
},
).addTo(layer);
渲染polygon数据出现漏洞
maptalks内默认会对图形进行simplify,这个问题是由于图形节点被简化导致的
你可以根据自己的需要设置简化的阈值
const polygon = new maptalks.Polygon(coordinates, {
simplifyTolerance: 2,
});
//update options
// polygon.config({
// simplifyTolerance: 1,
// });
simplifyTolerance
的值越大,简化的越狠,性能也越好,根据自己的需要设置合适值即可,默认值2,
如果你的数据渲染出现这种空洞, 可以将其设置为1试试 当然你可以简单的粗暴的你设置图形不要进行simplify
const polygon = new maptalks.Polygon(coordinates, {
enableSimplify: false,
});
//update options
// polygon.config({
// enableSimplify: false,
// });
GLTF模型透明度的问题
可以自己手动设置下 alphaTest
const symbol = {
url: "/resources/gltf/alien/alien.gltf",
shader: "pbr",
rotationZ: 180,
modelHeight: 240,
uniforms: {
alphaTest: 0.1,
},
};
// symbol.uniforms.alphaTest = 0.1;
GLTF模型线框效果
渲染模式切换到wireframe
即可
const point = new maptalks.GLTFMarker(center, {
symbol: {
url: "./../assets/data/rvE-hN_HeOPfqWbvA7ASi.glb",
modelHeight: 50, //model height,Unit is meters
scaleX: 1,
scaleY: 1,
scaleZ: 1,
rotationZ: 180,
shader: "wireframe",
},
});
如果想模型和线框同时显示,需要加两条同样的数据,只是渲染模式不同而已
const point = new maptalks.GLTFMarker(center, {
symbol: {
url: "./../assets/data/rvE-hN_HeOPfqWbvA7ASi.glb",
modelHeight: 50, //model height,Unit is meters
scaleX: 1,
scaleY: 1,
scaleZ: 1,
rotationZ: 180,
},
});
const point1 = point.copy();
point1.updateSymbol({
shader: "wireframe",
});
怎样弄个带海的图标效果
从效果图上看,使用单一的图形(Geometry)是无法做出这个效果,所以我们需要用多个图形来组成这个整体效果
- 垂直的海拔线我们用
LineString
来构造垂直的线 - 构造一个带海拔的
Marker
function addMarkers() {
data.features.forEach((f) => {
//随机海拔值
f.geometry.coordinates.push(Math.random() * 200 + 200);
});
const markers = maptalks.GeoJSON.toGeometry(data);
layer.addGeometry(markers);
}
function addLines() {
const lines = data.features.map((f) => {
//顶部坐标
const coordinates = f.geometry.coordinates;
//底部坐标
const bottomCoordinates = [...coordinates].slice(0, 2);
const line = new maptalks.LineString([bottomCoordinates, coordinates], {
//海拔线只是个效果,我们默认关闭事件交互可以有更好的性能
interactive: false,
});
return line;
});
layer.addGeometry(lines);
}
addMarkers();
addLines();
WARNING
如果使用 VectorLayer
要开启海拔选项
const layer = new maptalks.VectorLayer("layer", {
enableAltitude: true,
}).addTo(map);
GeoJSON数据怎样反序列化?
GIS项目会有大量的GeoJSON
数据,将GeoJSON
数据反序列化成maptalks的图形要素,看到不少同学都是用自己的 手动代码去反序列化的
不建议这么去做的,因为容易出错,尤其是对图形类型的判断,已经有好多同学在这里遇到问题了,最后发现问题都是出在 数据类型的判断上
GeoJSON.toGeometry
建议使用maptalks自带的GeoJSON.toGeometry
反序列化工具
const geos = maptalks.GeoJSON.toGeometry(geojson);
如果需要对图形做一些处理,比如样式啊,配置啊,对数据进行分类啊,可以自己遍历图形要素进行各种设置即可
const geos = maptalks.GeoJSON.toGeometry(geojson);
geos.forEach((geo) => {
//do some things setSymbol config,bind events etc
});
WARNING
注意
const geos = maptalks.GeoJSON.toGeometry(geojson);
的结果可能是 Geometry
或者Array<Geometry>
,不要直接使用geometry.addTo(layer)
方法,因为Array
上是没有 addTo
方法的,会报错的,应该使用
layer.addGeometry(geos);
GeoJSON.toGeometryAsync
GeoJSON.toGeometry 的异步版本,利用微任务解决大量GeoJSON反序列化卡的问题,其返回的是一个Promise,如果你的 GeoJSON数据比较大且卡主线程了,你可以使用它
const time = "parse geojson";
console.time(time);
let index = 0;
console.log("parse geojson start");
maptalks.GeoJSON.toGeometryAsync(geojson, (geo) => {
// geo.options.cursor = 'zoom-in';
geo.setId(index);
geo.setSymbol({
polygonFill: "green",
lineWidth: 0,
});
geo.on("mousemove", (e) => {
console.log(e.target.getId());
});
index++;
}).then((geos) => {
console.timeEnd(time);
console.log("parse geojson end");
layer.addGeometry(geos);
console.log("geomtries.length", layer.getCount());
});
WARNING
该特性要求maptalks版本 version>=v1.0.0-rc.29
怎样让图形在特定的地图层级范围内显示
maptalks的图层提供了minZoom
和maxZoom
配置选项来控制图层的层级显示范围,
const layer = new maptalks.VectorLayer("layer", {
minZoom: 15,
maxZoom: 18,
});
Geometry没有提供这样的设置,但是Geometry的样式里提供了visible
和opacity
等来控制样式的显示隐藏等,且支持function-type
, 我们可以利用function-type
来模拟minZoom
,maxZoom
的效果
假设有个Geometry想控制其
{
minZoom:15,
maxZoom:18
}
因为在[minZoom,maxZoom]区间的图形的可见性是一样的,这个很显然就适合用function-type
的 interval
来控制和模拟,因为interval
一个区间内输出的值是同一个
const point = new maptalks.GLTFMarker(center, {
symbol: {
url: "./../assets/data/alien.gltf",
modelHeight: 50, //model height,Unit is meters
scaleX: 1,
scaleY: 1,
scaleZ: 1,
rotationZ: 180,
visible: {
//mock minZoom=15,maxZoom=18
stops: [
[1, false],
[15.0, true],
[18.1, false],
[Infinity, false],
],
type: "interval",
},
},
});
- [0,15)区间内显然要隐藏Geometry
- [15,18]区间内要显示Geometry
- (18,Infinity)区间内要隐藏Geometry
编辑图形比较卡
因为编辑图形时是为每个顶点都创建一个控制点,且每个线段还要创建一个新建点,故而编辑点的数量为 2n-2,当图形的顶点数据比较多时,就会导致编辑的控制点非常多
你可以在开启编辑时开启碰撞检测,来减少当前点的密集度,从而有更好的性能表现
polygon.startEdit({
collision: true,
});
WARNING
该特性要求核心库 >=v1.0.0-rc.33
GeometryCollection
GeometryCollection 顾名思义表示Geometry
集合的意思,用来批量管理Geometry
- MultiPoint
- MultiLineString
- MultiPolygon
都是GeometryCollection的子类,在使用GeometryCollection时不要套娃,即不要将GeometryCollection自身和其子类作为 其子元素加入,否则可能会发生一些未知错误
const multiPoint = new maptalks.MultiPoint([]);
const geoCollection = new maptalks.GeometryCollection([multiPoint]);
WARNING
注意这个代码用来示范错误代码,业务里不要这样使用
arcgis json 转 GeoJSON
有时我们需要访问arcgis的数据服务,但是arcgis服务返回的是自己个json格式(esri json),不是 标准的GeoJSON格式,这时需要我们把arcgis json转成GeoJSON
参考仓库包:
TIP
该包仅供参考,具体是否好用和我也不知道
TIP
高版本的arcgis server是直接支持输出GeoJSON格式的,可以在请求服务是直接请求GeoJSON格式
大量数据的空间计算
前端的空间计算推荐 jsts
- turf不管是在性能和准确性都不好
- 服务端常用的库也是JTS,不管是应用层还是数据库层都是JTS的派生库,可以做到API等一致性
- JTS是经过大量的实践验证过的库
怎样解决大量数据的空间计算?
一般空间查询我们都是在服务端做的,例如使用(postgis),如果你的业务确实存在前端有大量的数据的情况下的空间计算, maptalks里提供了时间切片功能,我们可以利用他来解决前端大量数据的空间计算卡的问题
下面我们来演示个从1000w的点中查询点落在北京的点的集合
- 使用jsts完成空间计算
- 利用
runTaskAsync
来解决主线程卡的问题
构造1000w的点集合
因为1000w的点数据量真的太大了,无法使用静态的geojson数据,否则网络请求要好多时间,因而这里在简单的在内存里mock了数据
const minx = 73.66,
maxx = 135.05,
miny = 3.85,
maxy = 53.55;
const TENK = 10000;
function randomFeatures(callback) {
const dx = maxx - minx,
dy = maxy - miny;
const data = [];
const total = 1000 * TENK;
const pageSize = 10 * TENK;
const count = Math.ceil(total / pageSize);
const run = () => {
for (let i = 0, len = pageSize; i < len; i++) {
const x = Math.random() * dx + minx;
const y = Math.random() * dy + miny;
data.push({
type: "Feature",
geometry: {
coordinates: [x, y],
type: "Point",
},
});
}
};
runTaskAsync({ count, run }).then(() => {
callback(data);
});
}
使用runTaskAsync运行jsts空间查询
function query() {
showLoading("计算ing...");
debugLayer.clear();
const time = "randomFeatures";
console.time(time);
//生成测试数据
randomFeatures((data) => {
console.timeEnd(time);
const time1 = "sp query";
console.time(time1);
const pageSize = 2 * TENK;
const count = Math.ceil(data.length / pageSize);
let page = 1;
const run = () => {
const start = (page - 1) * pageSize,
end = page * pageSize;
const list = data.slice(start, end);
const geos = list.filter((feature) => {
const geo = geoJSONRender.read(feature);
return beijingGeo.geometry.intersects(geo.geometry);
});
page++;
return geos;
};
//运行空间查询函数
runTaskAsync({ count, run }).then((result) => {
console.timeEnd(time1);
const features = [];
result.forEach((f) => {
for (let i = 0, len = f.length; i < len; i++) {
features.push(f[i]);
}
});
const points = maptalks.GeoJSON.toGeometry(features, (geo) => {
geo.setSymbol({
markerType: "ellipse",
markerWidth: 6,
markerHeight: 6,
});
});
console.log(points);
debugLayer.addGeometry(points);
hideLoading();
});
});
}