Skip to content

Commit d11d813

Browse files
【update】优化提升 leaflet fgbLayer ol fgb source 渲染加载速度 review by songym
1 parent 3918cde commit d11d813

File tree

6 files changed

+410
-23
lines changed

6 files changed

+410
-23
lines changed

src/leaflet/core/Base.js

+167-12
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,182 @@ L.Control.Attribution.include({
1313
});
1414
L.Map.include({
1515
/*
16-
* 获取精确的像素坐标.
17-
* 当需要绘制比较平滑的曲线的时候可调用此方法代替latLngToContainerPoint
18-
* @param latlng
19-
*/
16+
* 获取精确的像素坐标.
17+
* 当需要绘制比较平滑的曲线的时候可调用此方法代替latLngToContainerPoint
18+
* @param latlng
19+
*/
2020
latLngToAccurateContainerPoint: function (latlng) {
2121
var projectedPoint = this.project(L.latLng(latlng));
2222
var layerPoint = projectedPoint._subtract(this.getPixelOrigin());
2323
return L.point(layerPoint).add(this._getMapPanePos());
2424
}
2525
});
26+
27+
var projectionNames = ['EPSG4326', 'EPSG3857', 'EPSG3395', 'EPSG900913'];
28+
29+
projectionNames.forEach(function (projectionName) {
30+
L.Util.extend(L.CRS[projectionName], {
31+
curScale: null,
32+
preZoom: 0,
33+
latLngToPoint: function (latlng, zoom) {
34+
var projectedPoint = this.projection.project(latlng);
35+
var scale;
36+
if (this.curScale && this.prevZoom === zoom) {
37+
scale = this.curScale;
38+
} else {
39+
scale = this.scale(zoom);
40+
this.curScale = scale;
41+
this.prevZoom = zoom;
42+
}
43+
return this.transformation._transform(projectedPoint, scale);
44+
}
45+
});
46+
});
47+
48+
L.Polyline.include({
49+
dirtyBounds: false,
50+
getBounds: function () {
51+
if (this.dirtyBounds && this._latlngs) {
52+
this._bounds = new L.latLngBounds();
53+
for (var i = 0, len = this._latlngs.length; i < len; i++) {
54+
this._bounds.extend(this._latlngs[i]);
55+
}
56+
this.dirtyBounds = false;
57+
}
58+
return this._bounds;
59+
},
60+
addLatLng: function (latlng, latlngs) {
61+
latlngs = latlngs || this._defaultShape();
62+
latlng = toLatLng(latlng);
63+
latlngs.push(latlng);
64+
this.dirtyBounds = true;
65+
return this.redraw();
66+
},
67+
_setLatLngs: function (latlngs) {
68+
this._latlngs = this._convertLatLngs(latlngs);
69+
},
70+
_convertLatLngs: function (latlngs) {
71+
var result = [],
72+
flat = L.LineUtil.isFlat(latlngs);
73+
74+
for (var i = 0, len = latlngs.length; i < len; i++) {
75+
if (flat) {
76+
result[i] = toLatLng(latlngs[i]);
77+
this.dirtyBounds = true;
78+
} else {
79+
result[i] = this._convertLatLngs(latlngs[i]);
80+
}
81+
}
82+
return result;
83+
},
84+
_project: function () {
85+
var pxBounds = new L.Bounds();
86+
this._rings = [];
87+
this._projectLatlngs(this._latlngs, this._rings, pxBounds);
88+
if (pxBounds.isValid()) {
89+
this._rawPxBounds = pxBounds;
90+
this._updateBounds();
91+
}
92+
}
93+
});
94+
95+
L.GeoJSON.include({
96+
initialize: function (geojson, options) {
97+
L.Util.setOptions(this, options);
98+
this._layers = {};
99+
this.defaultGeometryOptions = {};
100+
if (geojson) {
101+
this.addData(geojson);
102+
}
103+
},
104+
addData: function (geojson) {
105+
var features = Array.isArray(geojson) ? geojson : geojson.features,
106+
i, len, feature;
107+
108+
if (features) {
109+
for (i = 0, len = features.length; i < len; i++) {
110+
feature = features[i];
111+
if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
112+
this.addData(feature);
113+
}
114+
}
115+
return this;
116+
}
117+
118+
var options = this.options;
119+
120+
if (options.filter && !options.filter(geojson)) { return this; }
121+
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson;
122+
var layer = L.GeoJSON.geometryToLayer(geojson, options);
123+
if (!layer) {
124+
return this;
125+
}
126+
layer.feature = L.GeoJSON.asFeature(geojson);
127+
128+
layer.defaultOptions = layer.options;
129+
var defaultGeometryOptions = this.defaultGeometryOptions[geometry.type];
130+
if (defaultGeometryOptions) {
131+
layer.commonOptions = defaultGeometryOptions;
132+
} else {
133+
this.defaultGeometryOptions[geometry.type] = L.Util.extend({}, layer.defaultOptions);
134+
}
135+
this.resetStyle(layer);
136+
137+
if (options.onEachFeature) {
138+
options.onEachFeature(geojson, layer);
139+
}
140+
141+
return this.addLayer(layer);
142+
},
143+
resetStyle: function (layer) {
144+
if (layer === undefined) {
145+
return this.eachLayer(this.resetStyle, this);
146+
}
147+
if (layer.commonOptions) {
148+
layer.options = layer.commonOptions
149+
} else {
150+
layer.options = L.Util.extend({}, layer.defaultOptions);
151+
}
152+
this._setLayerStyle(layer, this.options.style);
153+
return this;
154+
}
155+
});
156+
26157
wrapToGeoJSON([L.Polyline, L.Polygon, L.Marker, L.CircleMarker, L.Circle, L.LayerGroup]);
27158

28159
function wrapToGeoJSON(objClassArray) {
29-
objClassArray.map((objClass) => {
30-
objClass.defaultFunction = objClass.prototype.toGeoJSON;
31-
objClass.include({
32-
toGeoJSON: function (precision) {
33-
return objClass.defaultFunction.call(this, precision || L.toGeoJSONPrecision || 15);
34-
}
35-
})
36-
return objClass;
160+
objClassArray.map((objClass) => {
161+
objClass.defaultFunction = objClass.prototype.toGeoJSON;
162+
objClass.include({
163+
toGeoJSON: function (precision) {
164+
return objClass.defaultFunction.call(this, precision || L.toGeoJSONPrecision || 15);
165+
}
37166
})
167+
return objClass;
168+
})
169+
}
38170

171+
function toLatLng(a, b, c) {
172+
if (a instanceof L.latLng) {
173+
return a;
174+
}
175+
if (Array.isArray(a) && typeof a[0] !== 'object') {
176+
if (a.length === 3) {
177+
return new L.latLng(a[0], a[1], a[2]);
178+
}
179+
if (a.length === 2) {
180+
return new L.latLng(a[0], a[1]);
181+
}
182+
return null;
183+
}
184+
if (a === undefined || a === null) {
185+
return a;
186+
}
187+
if (typeof a === 'object' && 'lat' in a) {
188+
return new L.latLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
189+
}
190+
if (b === undefined) {
191+
return null;
192+
}
193+
return new L.latLng(a, b, c);
39194
}

src/leaflet/overlay/FGBLayer.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import RBush from 'rbush';
77
import { getIntersection } from '@supermapgis/iclient-common/util/MapCalculateUtil';
88
import { FetchRequest } from '@supermapgis/iclient-common/util/FetchRequest';
99
import { deserialize } from 'flatgeobuf/lib/mjs/geojson';
10+
import throttle from 'lodash.throttle';
1011

1112
/**
1213
* @class FGBLayer
@@ -28,6 +29,7 @@ import { deserialize } from 'flatgeobuf/lib/mjs/geojson';
2829
* @param {boolean} [options.idField='SmID'] - 是否指定要素字段作为唯一 ID,当 strategy 为 bbox 时生效。
2930
* @param {function} [options.featureLoader] - 要素自定义方法。
3031
* @param {function} [options.onEachFeature] - 要素创建时调用。
32+
* @param {number} [options.renderInterval=500] - 渲染间隔时间。
3133
* @usage
3234
* ```
3335
* // 浏览器
@@ -59,6 +61,8 @@ export var FGBLayer = L.FeatureGroup.extend({
5961
this._checked = false;
6062
this.idField = this.options.idField || 'SmID';
6163
this._updateFeaturesFn = this._updateFeatures.bind(this);
64+
this.renderInterval = this.options.renderInterval !== undefined ? this.options.renderInterval : 500;
65+
this.options.noClip = true;
6266
L.Util.setOptions(this, options);
6367
},
6468
onAdd: function (map) {
@@ -117,6 +121,11 @@ export var FGBLayer = L.FeatureGroup.extend({
117121
this.previousLayer = this.curLayer;
118122
this.curLayer.addTo(this);
119123
}
124+
let featureList = [];
125+
const throttleFn = throttle(()=>{
126+
this.curLayer.addData(featureList);
127+
featureList = [];
128+
}, this.renderInterval, { trailing: true, leading: false })
120129
for await (let feature of fgb) {
121130
if (this.strategy === 'bbox') {
122131
let id = feature.properties[this.idField];
@@ -134,7 +143,8 @@ export var FGBLayer = L.FeatureGroup.extend({
134143
if (this.options.featureLoader && typeof this.options.featureLoader === 'function') {
135144
feature = this.options.featureLoader(feature);
136145
}
137-
this.curLayer.addData(feature);
146+
featureList.push(feature);
147+
throttleFn();
138148
}
139149
},
140150
async _getStream(url) {

src/openlayers/overlay/FGB.js

+68-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { FetchRequest } from '@supermapgis/iclient-common/util/FetchRequest';
77
import { all, bbox } from 'ol/loadingstrategy';
88
import { getIntersection } from '@supermapgis/iclient-common/util/MapCalculateUtil';
99
import GeoJSON from 'ol/format/GeoJSON';
10+
import throttle from 'lodash.throttle';
11+
import { getUid } from 'ol/util';
12+
1013
/**
1114
* @class FGB
1215
* @browsernamespace ol.source
@@ -26,12 +29,50 @@ import GeoJSON from 'ol/format/GeoJSON';
2629
* @param {boolean} [opt_options.useSpatialIndex] - 是否启用要素空间索引。
2730
* @param {boolean} [opt_options.wrapX] - 是否平铺地图。
2831
* @param {boolean} [opt_options.idField='SmID'] - 要素属性中表示唯一标识的字段,当 strategy 为 ol.loadingstrategy.bbox 时生效。
32+
* @param {number} [opt_options.renderInterval=500] - 渲染间隔时间。
2933
* @extends {ol.source.Vector}
3034
* @usage
3135
*/
36+
37+
class FeaturesCollection {
38+
constructor() {
39+
this.features = [];
40+
}
41+
42+
clear() {
43+
this.features = [];
44+
}
45+
46+
push(feature) {
47+
this.features.push(feature);
48+
}
49+
50+
isEmpty() {
51+
return this.features.length === 0;
52+
}
53+
getArray() {
54+
return this.features;
55+
}
56+
57+
forEach(fn) {
58+
return this.features.forEach(fn);
59+
}
60+
61+
getLength() {
62+
return this.features.length;
63+
}
64+
65+
remove(feature) {
66+
const index = this.features.indexOf(feature);
67+
if (index !== -1) {
68+
this.features.splice(index, 1);
69+
}
70+
}
71+
}
72+
3273
export class FGB extends VectorSource {
3374
constructor(options) {
34-
const baseOptions = Object.assign({ strategy: bbox }, options);
75+
const baseOptions = Object.assign({ strategy: bbox, useSpatialIndex: false }, options);
3576
delete baseOptions.url;
3677
delete baseOptions.extent;
3778
delete baseOptions.idField;
@@ -42,7 +83,8 @@ export class FGB extends VectorSource {
4283
this.extent = this.options.extent;
4384
this._idField = this.options.idField || 'SmID';
4485
this._validatedId = false;
45-
this._checked = false;
86+
this._checked = false;
87+
this.renderInterval = this.options.renderInterval !== undefined ? this.options.renderInterval : 500;
4688
this.setLoader(async function (extent) {
4789
if (this.strategy === bbox) {
4890
if (!this._validatedId) {
@@ -75,6 +117,11 @@ export class FGB extends VectorSource {
75117
};
76118
}
77119
const fgb = deserialize(url, rect);
120+
let featureList = [];
121+
const throttleFn = throttle(() => {
122+
this.addFeatures(featureList);
123+
featureList = [];
124+
}, this.renderInterval, { trailing: true, leading: false });
78125
for await (let feature of fgb) {
79126
let id = feature.properties[this._idField];
80127
if (id && !this._validatedId) {
@@ -88,7 +135,8 @@ export class FGB extends VectorSource {
88135
if (this.options.featureLoader && typeof this.options.featureLoader === 'function') {
89136
feature = this.options.featureLoader(feature);
90137
}
91-
this.addFeature(feature);
138+
featureList.push(feature);
139+
throttleFn();
92140
}
93141
}
94142

@@ -97,4 +145,21 @@ export class FGB extends VectorSource {
97145
return response;
98146
});
99147
}
148+
149+
bindFeaturesCollection_() {
150+
this.featuresCollection_ = new FeaturesCollection();
151+
}
152+
153+
addFeaturesInternal(features) {
154+
for (let i = 0, length = features.length; i < length; i++) {
155+
const feature = features[i];
156+
const featureKey = getUid(feature);
157+
this.setupChangeEvents_(featureKey, feature);
158+
this.featuresCollection_.push(features[i]);
159+
const geometry = feature.getGeometry();
160+
if (!geometry) {
161+
this.nullGeometryFeatures_[featureKey] = feature;
162+
}
163+
}
164+
}
100165
}

0 commit comments

Comments
 (0)