@guardian/interactive-component-library 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/molecules/canvas-map/lib/Feature.d.ts +4 -1
- package/dist/components/molecules/canvas-map/lib/Feature.js +3 -0
- package/dist/components/molecules/canvas-map/lib/layers/TextLayer.d.ts +65 -16
- package/dist/components/molecules/canvas-map/lib/layers/TextLayer.js +28 -4
- package/dist/components/molecules/canvas-map/lib/layers/VectorLayer.d.ts +36 -10
- package/dist/components/molecules/canvas-map/lib/layers/VectorLayer.js +6 -3
- package/dist/components/molecules/canvas-map/lib/renderers/FeatureRenderer.js +0 -2
- package/dist/components/molecules/canvas-map/lib/renderers/MapRenderer.d.ts +14 -0
- package/dist/components/molecules/canvas-map/lib/renderers/MapRenderer.js +52 -8
- package/dist/components/molecules/canvas-map/lib/renderers/TextLayerRenderer.d.ts +61 -5
- package/dist/components/molecules/canvas-map/lib/renderers/TextLayerRenderer.js +224 -26
- package/dist/components/molecules/canvas-map/lib/renderers/VectorLayerRenderer.d.ts +13 -6
- package/dist/components/molecules/canvas-map/lib/renderers/VectorLayerRenderer.js +12 -42
- package/dist/components/molecules/canvas-map/lib/sources/VectorSource.d.ts +24 -6
- package/dist/components/molecules/canvas-map/lib/sources/VectorSource.js +18 -0
- package/dist/components/molecules/canvas-map/lib/styles/Style.d.ts +42 -11
- package/dist/components/molecules/canvas-map/lib/styles/Style.js +7 -0
- package/dist/components/molecules/canvas-map/lib/styles/Text.d.ts +184 -11
- package/dist/components/molecules/canvas-map/lib/styles/Text.js +18 -3
- package/dist/components/molecules/result-summary/index.d.ts +3 -1
- package/dist/components/molecules/result-summary/index.js +38 -5
- package/dist/components/molecules/result-summary/style.module.css.js +12 -3
- package/dist/style.css +21 -2
- package/package.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { FeatureRenderer } from "./FeatureRenderer.js";
|
|
2
2
|
import { replaceChildren } from "../util/dom.js";
|
|
3
|
+
import { MapEvent } from "../events/MapEvent.js";
|
|
3
4
|
class TextLayerRenderer {
|
|
5
|
+
/**
|
|
6
|
+
* @param {import("../layers/TextLayer").TextLayer} layer
|
|
7
|
+
*/
|
|
4
8
|
constructor(layer) {
|
|
5
9
|
this.layer = layer;
|
|
6
10
|
this.featureRenderer = new FeatureRenderer();
|
|
@@ -10,16 +14,23 @@ class TextLayerRenderer {
|
|
|
10
14
|
style.position = "absolute";
|
|
11
15
|
style.width = "100%";
|
|
12
16
|
style.height = "100%";
|
|
13
|
-
style.pointerEvents = "none";
|
|
14
17
|
style.overflow = "hidden";
|
|
18
|
+
style.pointerEvents = "none";
|
|
19
|
+
this._mouseInteractionsEnabled = this.layer.onClick || this.layer.onHover || this.layer.restyleOnHover;
|
|
20
|
+
this.attachClickAndHoverListeners();
|
|
15
21
|
}
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @param {import("./MapRenderer").CanvasSingleton} canvasSingleton
|
|
24
|
+
*/
|
|
25
|
+
renderFrame(frameState, canvasSingleton) {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
if (this.layer.opacity === 0) return null;
|
|
18
28
|
const { declutterTree } = frameState;
|
|
19
29
|
const { projection, viewPortSize, sizeInPixels, visibleExtent, transform } = frameState.viewState;
|
|
20
|
-
this._element.style.opacity = this.layer.opacity
|
|
30
|
+
this._element.style.opacity = `${this.layer.opacity}`;
|
|
21
31
|
const source = this.layer.source;
|
|
22
32
|
const features = source.getFeaturesInExtent(visibleExtent);
|
|
33
|
+
let canvasCtx;
|
|
23
34
|
const textElements = [];
|
|
24
35
|
for (const feature of features) {
|
|
25
36
|
const geometries = feature.getProjectedGeometries(projection);
|
|
@@ -30,16 +41,28 @@ class TextLayerRenderer {
|
|
|
30
41
|
);
|
|
31
42
|
}
|
|
32
43
|
const styleFunction = feature.getStyleFunction() || this.layer.getStyleFunction();
|
|
33
|
-
const featureStyle = styleFunction(
|
|
44
|
+
const featureStyle = styleFunction(
|
|
45
|
+
feature,
|
|
46
|
+
transform.k,
|
|
47
|
+
this._hoveredFeature === feature
|
|
48
|
+
);
|
|
34
49
|
const textElement = this.getTextElementWithID(feature.uid);
|
|
35
50
|
textElement.innerText = featureStyle.text.content;
|
|
36
|
-
const [
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.
|
|
42
|
-
|
|
51
|
+
const [canvasX, canvasY] = transform.apply(point.coordinates);
|
|
52
|
+
const [relativeX, relativeY] = [
|
|
53
|
+
canvasX / sizeInPixels[0],
|
|
54
|
+
canvasY / sizeInPixels[1]
|
|
55
|
+
];
|
|
56
|
+
const position = this.getElementPosition(featureStyle.text, {
|
|
57
|
+
x: relativeX,
|
|
58
|
+
y: relativeY
|
|
59
|
+
});
|
|
60
|
+
const elementDimens = this.styleTextElement(
|
|
61
|
+
textElement,
|
|
62
|
+
featureStyle.text,
|
|
63
|
+
position
|
|
64
|
+
);
|
|
65
|
+
const bbox = this.getElementBBox(elementDimens, featureStyle.text, {
|
|
43
66
|
x: relativeX * viewPortSize[0],
|
|
44
67
|
y: relativeY * viewPortSize[1]
|
|
45
68
|
});
|
|
@@ -49,6 +72,46 @@ class TextLayerRenderer {
|
|
|
49
72
|
}
|
|
50
73
|
declutterTree.insert(bbox);
|
|
51
74
|
}
|
|
75
|
+
const callout = (_a = featureStyle == null ? void 0 : featureStyle.text) == null ? void 0 : _a.callout;
|
|
76
|
+
const icon = (_b = featureStyle == null ? void 0 : featureStyle.text) == null ? void 0 : _b.icon;
|
|
77
|
+
if (callout || icon) {
|
|
78
|
+
canvasCtx ?? (canvasCtx = canvasSingleton.getContext2d());
|
|
79
|
+
}
|
|
80
|
+
if (callout) {
|
|
81
|
+
const canvasOffsetX = (callout.offsetBy.x - callout.leaderGap) * window.devicePixelRatio;
|
|
82
|
+
const canvasOffsetY = callout.offsetBy.y * window.devicePixelRatio;
|
|
83
|
+
canvasCtx.beginPath();
|
|
84
|
+
canvasCtx.moveTo(canvasX, canvasY);
|
|
85
|
+
canvasCtx.lineTo(canvasX + canvasOffsetX / 2, canvasY + canvasOffsetY);
|
|
86
|
+
canvasCtx.moveTo(canvasX + canvasOffsetX / 2, canvasY + canvasOffsetY);
|
|
87
|
+
canvasCtx.lineTo(canvasX + canvasOffsetX, canvasY + canvasOffsetY);
|
|
88
|
+
canvasCtx.strokeStyle = callout.leaderColor;
|
|
89
|
+
canvasCtx.lineWidth = callout.leaderWidth;
|
|
90
|
+
canvasCtx.stroke();
|
|
91
|
+
canvasCtx.closePath();
|
|
92
|
+
}
|
|
93
|
+
if (icon) {
|
|
94
|
+
canvasCtx.beginPath();
|
|
95
|
+
canvasCtx.save();
|
|
96
|
+
let iconPosX = relativeX * viewPortSize[0];
|
|
97
|
+
let iconPosY = relativeY * viewPortSize[1];
|
|
98
|
+
if (callout) {
|
|
99
|
+
iconPosX += callout.offsetBy.x;
|
|
100
|
+
iconPosY += callout.offsetBy.y;
|
|
101
|
+
}
|
|
102
|
+
if (icon.position === "right") {
|
|
103
|
+
iconPosX += elementDimens.width;
|
|
104
|
+
} else if (icon.position === "left") {
|
|
105
|
+
iconPosX += icon.padding + icon.size / 2;
|
|
106
|
+
}
|
|
107
|
+
canvasCtx.translate(
|
|
108
|
+
iconPosX * window.devicePixelRatio,
|
|
109
|
+
iconPosY * window.devicePixelRatio
|
|
110
|
+
);
|
|
111
|
+
this.drawTextIcon(canvasCtx, icon);
|
|
112
|
+
canvasCtx.restore();
|
|
113
|
+
canvasCtx.closePath();
|
|
114
|
+
}
|
|
52
115
|
if (this.layer.drawCollisionBoxes) {
|
|
53
116
|
const collisionBoxDebugElement = this.getCollisionBoxElement(bbox);
|
|
54
117
|
textElements.push(collisionBoxDebugElement);
|
|
@@ -64,9 +127,19 @@ class TextLayerRenderer {
|
|
|
64
127
|
if (!textElement) {
|
|
65
128
|
textElement = document.createElement("div");
|
|
66
129
|
textElement.id = elementId;
|
|
130
|
+
textElement.dataset.featureId = id;
|
|
131
|
+
}
|
|
132
|
+
if (this._mouseInteractionsEnabled) {
|
|
133
|
+
textElement.style.pointerEvents = "auto";
|
|
134
|
+
textElement.style.cursor = "pointer";
|
|
67
135
|
}
|
|
68
136
|
return textElement;
|
|
69
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* @param {HTMLDivElement} element
|
|
140
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
141
|
+
* @param {{left: string, top: string}} position
|
|
142
|
+
*/
|
|
70
143
|
styleTextElement(element, textStyle, position) {
|
|
71
144
|
const style = element.style;
|
|
72
145
|
style.position = "absolute";
|
|
@@ -80,8 +153,22 @@ class TextLayerRenderer {
|
|
|
80
153
|
style.lineHeight = textStyle.lineHeight;
|
|
81
154
|
style.color = textStyle.color;
|
|
82
155
|
style.textShadow = textStyle.textShadow;
|
|
83
|
-
|
|
156
|
+
let { width, height } = this.getElementSize(element);
|
|
157
|
+
if (textStyle.icon) {
|
|
158
|
+
const iconSize = textStyle.icon.size;
|
|
159
|
+
if (textStyle.icon.position === "left") {
|
|
160
|
+
style.paddingLeft = `${iconSize + textStyle.icon.padding * 2}px`;
|
|
161
|
+
} else if (textStyle.icon.position === "right") {
|
|
162
|
+
style.paddingRight = `${iconSize + textStyle.icon.padding * 2}px`;
|
|
163
|
+
}
|
|
164
|
+
const iconSizeHeightDiff = iconSize - height;
|
|
165
|
+
if (iconSizeHeightDiff > 0) {
|
|
166
|
+
style.paddingTop = `${iconSizeHeightDiff / 2}px`;
|
|
167
|
+
style.paddingBottom = `${iconSizeHeightDiff / 2}px`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
84
170
|
style.transform = textStyle.getTransform(width, height);
|
|
171
|
+
return { width, height };
|
|
85
172
|
}
|
|
86
173
|
getElementSize(element) {
|
|
87
174
|
if (!element.parentElement) {
|
|
@@ -93,29 +180,57 @@ class TextLayerRenderer {
|
|
|
93
180
|
}
|
|
94
181
|
return { width, height };
|
|
95
182
|
}
|
|
96
|
-
|
|
97
|
-
|
|
183
|
+
/**
|
|
184
|
+
* @param {{ height: number, width: number }} dimens
|
|
185
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
186
|
+
* @param {{x: number, y: number}} position
|
|
187
|
+
*/
|
|
188
|
+
getElementBBox(dimens, textStyle, position) {
|
|
189
|
+
const collisionPadding = {
|
|
98
190
|
top: 2,
|
|
99
191
|
right: 2,
|
|
100
192
|
bottom: 2,
|
|
101
193
|
left: 2
|
|
102
194
|
};
|
|
103
|
-
const { width, height } = this.getElementSize(element);
|
|
104
195
|
const { x: translateX, y: translateY } = textStyle.getTranslation(
|
|
105
|
-
width,
|
|
106
|
-
height
|
|
196
|
+
dimens.width,
|
|
197
|
+
dimens.height
|
|
107
198
|
);
|
|
108
|
-
|
|
109
|
-
|
|
199
|
+
let minX = position.x + translateX - collisionPadding.left;
|
|
200
|
+
let minY = position.y + translateY - collisionPadding.top;
|
|
201
|
+
let maxX = minX + dimens.width + collisionPadding.left + collisionPadding.right;
|
|
202
|
+
let maxY = minY + dimens.height + collisionPadding.top + collisionPadding.bottom;
|
|
203
|
+
if (textStyle.callout) {
|
|
204
|
+
minX += textStyle.callout.offsetBy.x;
|
|
205
|
+
minY += textStyle.callout.offsetBy.y;
|
|
206
|
+
maxX += textStyle.callout.offsetBy.x;
|
|
207
|
+
maxY += textStyle.callout.offsetBy.y;
|
|
208
|
+
}
|
|
209
|
+
minX = Math.floor(minX);
|
|
210
|
+
minY = Math.floor(minY);
|
|
211
|
+
maxX = Math.ceil(maxX);
|
|
212
|
+
maxY = Math.ceil(maxY);
|
|
110
213
|
return {
|
|
111
214
|
minX,
|
|
112
215
|
minY,
|
|
113
|
-
maxX
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
216
|
+
maxX,
|
|
217
|
+
maxY
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* @param {import("../styles/Text").Text} textStyle
|
|
222
|
+
* @param {{x: number, y: number}} position
|
|
223
|
+
*/
|
|
224
|
+
getElementPosition(textStyle, position) {
|
|
225
|
+
if (textStyle.callout) {
|
|
226
|
+
return {
|
|
227
|
+
left: `calc(${position.x * 100}% + ${textStyle.callout.offsetBy.x}px)`,
|
|
228
|
+
top: `calc(${position.y * 100}% + ${textStyle.callout.offsetBy.y}px)`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
left: `${position.x * 100}%`,
|
|
233
|
+
top: `${position.y * 100}%`
|
|
119
234
|
};
|
|
120
235
|
}
|
|
121
236
|
getCollisionBoxElement(bbox) {
|
|
@@ -129,6 +244,89 @@ class TextLayerRenderer {
|
|
|
129
244
|
style.border = "1px solid red";
|
|
130
245
|
return element;
|
|
131
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Draws a `Text` element's icon on the canvas.
|
|
249
|
+
*
|
|
250
|
+
* Expects the canvas context to be translated to the text element's position.
|
|
251
|
+
*
|
|
252
|
+
* TODO: support more shapes?
|
|
253
|
+
*
|
|
254
|
+
* @param {CanvasRenderingContext2D} context
|
|
255
|
+
* @param {import("../styles/Text").IconOptions} icon
|
|
256
|
+
*/
|
|
257
|
+
drawTextIcon(context, icon) {
|
|
258
|
+
var _a;
|
|
259
|
+
if (icon.shape === "circle") {
|
|
260
|
+
context.arc(
|
|
261
|
+
0,
|
|
262
|
+
0,
|
|
263
|
+
icon.size * devicePixelRatio / 2,
|
|
264
|
+
0,
|
|
265
|
+
2 * Math.PI,
|
|
266
|
+
false
|
|
267
|
+
);
|
|
268
|
+
if (icon.style) {
|
|
269
|
+
(_a = icon.style.fill) == null ? void 0 : _a.drawInContext(context);
|
|
270
|
+
if (icon.style.stroke) {
|
|
271
|
+
context.lineWidth = icon.style.stroke.width;
|
|
272
|
+
context.strokeStyle = icon.style.stroke.getRgba();
|
|
273
|
+
context.stroke();
|
|
274
|
+
}
|
|
275
|
+
} else if (icon.color) {
|
|
276
|
+
context.fillStyle = icon.color;
|
|
277
|
+
context.fill();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
attachClickAndHoverListeners() {
|
|
282
|
+
if (this.layer.onClick) {
|
|
283
|
+
this._element.addEventListener("click", (event) => {
|
|
284
|
+
if (!event.target) return;
|
|
285
|
+
const clickedFeature = this.layer.source.getFeatures().find((feature) => {
|
|
286
|
+
var _a;
|
|
287
|
+
return ((_a = event.target.dataset) == null ? void 0 : _a.featureId) === feature.uid;
|
|
288
|
+
});
|
|
289
|
+
if (!clickedFeature) return;
|
|
290
|
+
this.layer.onClick(clickedFeature, event);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (this.layer.onHover) {
|
|
294
|
+
this._element.addEventListener("mouseover", (event) => {
|
|
295
|
+
if (!event.target) return;
|
|
296
|
+
const hoveredFeature = this.layer.source.getFeatures().find((feature) => {
|
|
297
|
+
var _a;
|
|
298
|
+
return ((_a = event.target.dataset) == null ? void 0 : _a.featureId) === feature.uid;
|
|
299
|
+
});
|
|
300
|
+
if (!hoveredFeature) return;
|
|
301
|
+
const onHoverLeave = this.layer.onHover(hoveredFeature, event);
|
|
302
|
+
if (onHoverLeave) {
|
|
303
|
+
this._element.addEventListener("mouseout", onHoverLeave, {
|
|
304
|
+
once: true
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if (this.layer.restyleOnHover) {
|
|
310
|
+
this._element.addEventListener("mouseover", (event) => {
|
|
311
|
+
if (!event.target) return;
|
|
312
|
+
const hoveredFeature = this.layer.source.getFeatures().find((feature) => {
|
|
313
|
+
var _a;
|
|
314
|
+
return ((_a = event.target.dataset) == null ? void 0 : _a.featureId) === feature.uid;
|
|
315
|
+
});
|
|
316
|
+
if (!hoveredFeature) return;
|
|
317
|
+
this._hoveredFeature = hoveredFeature;
|
|
318
|
+
this.layer.dispatcher.dispatch(MapEvent.CHANGE);
|
|
319
|
+
this._element.addEventListener(
|
|
320
|
+
"mouseout",
|
|
321
|
+
() => {
|
|
322
|
+
this._hoveredFeature = void 0;
|
|
323
|
+
this.layer.dispatcher.dispatch(MapEvent.CHANGE);
|
|
324
|
+
},
|
|
325
|
+
{ once: true }
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
132
330
|
}
|
|
133
331
|
export {
|
|
134
332
|
TextLayerRenderer
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { FeatureRenderer } from './FeatureRenderer';
|
|
2
2
|
export class VectorLayerRenderer {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @constructor
|
|
5
|
+
* @param {import('../layers').VectorLayer} layer
|
|
6
|
+
*/
|
|
7
|
+
constructor(layer: import('../layers').VectorLayer);
|
|
8
|
+
/**
|
|
9
|
+
* @type {import('../layers').VectorLayer}
|
|
10
|
+
*/
|
|
11
|
+
layer: import('../layers').VectorLayer;
|
|
5
12
|
featureRenderer: FeatureRenderer;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("./MapRenderer").CanvasSingleton} canvasSingleton
|
|
15
|
+
*/
|
|
16
|
+
renderFrame(frameState: any, canvasSingleton: import('./MapRenderer').CanvasSingleton): any;
|
|
10
17
|
}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { FeatureRenderer } from "./FeatureRenderer.js";
|
|
2
2
|
class VectorLayerRenderer {
|
|
3
|
+
/**
|
|
4
|
+
* @constructor
|
|
5
|
+
* @param {import('../layers').VectorLayer} layer
|
|
6
|
+
*/
|
|
3
7
|
constructor(layer) {
|
|
4
8
|
this.layer = layer;
|
|
5
9
|
this.featureRenderer = new FeatureRenderer();
|
|
6
10
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @param {import("./MapRenderer").CanvasSingleton} canvasSingleton
|
|
13
|
+
*/
|
|
14
|
+
renderFrame(frameState, canvasSingleton) {
|
|
15
|
+
if (this.layer.opacity === 0) return null;
|
|
16
|
+
const { projection, visibleExtent, transform } = frameState.viewState;
|
|
17
|
+
const context = canvasSingleton.getContext2d();
|
|
12
18
|
context.save();
|
|
13
19
|
context.translate(transform.x, transform.y);
|
|
14
20
|
context.scale(transform.k, transform.k);
|
|
@@ -19,7 +25,7 @@ class VectorLayerRenderer {
|
|
|
19
25
|
const features = source.getFeaturesInExtent(visibleExtent);
|
|
20
26
|
for (const feature of features) {
|
|
21
27
|
const styleFunction = feature.getStyleFunction() || this.layer.getStyleFunction();
|
|
22
|
-
const featureStyle = styleFunction(feature);
|
|
28
|
+
const featureStyle = styleFunction(feature, transform.k);
|
|
23
29
|
if ((featureStyle == null ? void 0 : featureStyle.stroke) || (featureStyle == null ? void 0 : featureStyle.fill)) {
|
|
24
30
|
context.save();
|
|
25
31
|
this.featureRenderer.setStyle(featureStyle);
|
|
@@ -36,42 +42,6 @@ class VectorLayerRenderer {
|
|
|
36
42
|
context.stroke();
|
|
37
43
|
}
|
|
38
44
|
context.restore();
|
|
39
|
-
return container;
|
|
40
|
-
}
|
|
41
|
-
getOrCreateContainer(targetElement, sizeInPixels) {
|
|
42
|
-
let container = null;
|
|
43
|
-
let containerReused = false;
|
|
44
|
-
let canvas = targetElement && targetElement.firstElementChild;
|
|
45
|
-
if (canvas instanceof HTMLCanvasElement) {
|
|
46
|
-
container = targetElement;
|
|
47
|
-
containerReused = true;
|
|
48
|
-
} else if (this._container) {
|
|
49
|
-
container = this._container;
|
|
50
|
-
} else {
|
|
51
|
-
container = this.createContainer();
|
|
52
|
-
}
|
|
53
|
-
if (!containerReused) {
|
|
54
|
-
const canvas2 = container.firstElementChild;
|
|
55
|
-
canvas2.width = sizeInPixels[0];
|
|
56
|
-
canvas2.height = sizeInPixels[1];
|
|
57
|
-
}
|
|
58
|
-
this._container = container;
|
|
59
|
-
return container;
|
|
60
|
-
}
|
|
61
|
-
createContainer() {
|
|
62
|
-
const container = document.createElement("div");
|
|
63
|
-
container.className = "gv-map-layer";
|
|
64
|
-
let style = container.style;
|
|
65
|
-
style.position = "absolute";
|
|
66
|
-
style.width = "100%";
|
|
67
|
-
style.height = "100%";
|
|
68
|
-
const canvas = document.createElement("canvas");
|
|
69
|
-
style = canvas.style;
|
|
70
|
-
style.position = "absolute";
|
|
71
|
-
style.width = "100%";
|
|
72
|
-
style.height = "100%";
|
|
73
|
-
container.appendChild(canvas);
|
|
74
|
-
return container;
|
|
75
45
|
}
|
|
76
46
|
}
|
|
77
47
|
export {
|
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import { Dispatcher } from '../events';
|
|
2
2
|
import { default as RBush } from 'rbush';
|
|
3
3
|
export class VectorSource {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} props
|
|
6
|
+
* @param {import("../Feature").Feature[]} props.features
|
|
7
|
+
*/
|
|
4
8
|
constructor({ features }: {
|
|
5
|
-
features:
|
|
9
|
+
features: import('../Feature').Feature[];
|
|
6
10
|
});
|
|
7
11
|
dispatcher: Dispatcher;
|
|
8
12
|
_featuresRtree: RBush;
|
|
9
13
|
tearDown(): void;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @returns {import("../Feature").Feature[]}
|
|
16
|
+
*/
|
|
17
|
+
getFeatures(): import('../Feature').Feature[];
|
|
18
|
+
/**
|
|
19
|
+
* @param {[number, number]} coordinate
|
|
20
|
+
* @returns {import("../Feature").Feature[]}
|
|
21
|
+
*/
|
|
22
|
+
getFeaturesAtCoordinate(coordinate: [number, number]): import('../Feature').Feature[];
|
|
23
|
+
/**
|
|
24
|
+
* @param {[number, number, number, number]} extent TODO: should this be an `Extent`?
|
|
25
|
+
* @returns {import("../Feature").Feature[]}
|
|
26
|
+
*/
|
|
27
|
+
getFeaturesInExtent(extent: [number, number, number, number]): import('../Feature').Feature[];
|
|
28
|
+
/**
|
|
29
|
+
* @param {import("../Feature").Feature[]} features
|
|
30
|
+
*/
|
|
31
|
+
setFeatures(features: import('../Feature').Feature[]): void;
|
|
32
|
+
_features: import('../Feature').Feature[];
|
|
15
33
|
}
|
|
@@ -3,6 +3,10 @@ import knn from "rbush-knn";
|
|
|
3
3
|
import { Dispatcher } from "../events/Dispatcher.js";
|
|
4
4
|
import { MapEvent } from "../events/MapEvent.js";
|
|
5
5
|
class VectorSource {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} props
|
|
8
|
+
* @param {import("../Feature").Feature[]} props.features
|
|
9
|
+
*/
|
|
6
10
|
constructor({ features }) {
|
|
7
11
|
this.dispatcher = new Dispatcher(this);
|
|
8
12
|
this._featuresRtree = new RBush();
|
|
@@ -11,9 +15,16 @@ class VectorSource {
|
|
|
11
15
|
tearDown() {
|
|
12
16
|
this.dispatcher = null;
|
|
13
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* @returns {import("../Feature").Feature[]}
|
|
20
|
+
*/
|
|
14
21
|
getFeatures() {
|
|
15
22
|
return this._features;
|
|
16
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* @param {[number, number]} coordinate
|
|
26
|
+
* @returns {import("../Feature").Feature[]}
|
|
27
|
+
*/
|
|
17
28
|
getFeaturesAtCoordinate(coordinate) {
|
|
18
29
|
const [x, y] = coordinate;
|
|
19
30
|
const items = knn(this._featuresRtree, x, y, 10, (d) => {
|
|
@@ -27,11 +38,18 @@ class VectorSource {
|
|
|
27
38
|
items.sort((a, b) => a.distance - b.distance);
|
|
28
39
|
return items.map((d) => d.feature);
|
|
29
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* @param {[number, number, number, number]} extent TODO: should this be an `Extent`?
|
|
43
|
+
* @returns {import("../Feature").Feature[]}
|
|
44
|
+
*/
|
|
30
45
|
getFeaturesInExtent(extent) {
|
|
31
46
|
const [minX, minY, maxX, maxY] = extent;
|
|
32
47
|
const features = this._featuresRtree.search({ minX, minY, maxX, maxY }).map((d) => d.feature);
|
|
33
48
|
return features;
|
|
34
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* @param {import("../Feature").Feature[]} features
|
|
52
|
+
*/
|
|
35
53
|
setFeatures(features) {
|
|
36
54
|
this._featuresRtree.clear();
|
|
37
55
|
for (const feature of features) {
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { Stroke } from './Stroke';
|
|
2
|
+
import { Fill } from './Fill';
|
|
3
|
+
import { Text } from './Text';
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
5
|
+
* @import { Text } from "./Text";
|
|
6
|
+
* @import { Stroke } from "./Stroke"
|
|
7
|
+
* @import { Fill } from "./Fill"
|
|
3
8
|
*
|
|
4
|
-
* @
|
|
9
|
+
* @callback StyleFunction
|
|
10
|
+
* @param {import("../Feature").Feature} feature The feature to style.
|
|
11
|
+
* @param {number} zoom The current map zoom level
|
|
12
|
+
* @param {boolean} [isHovering] If the layer has `restyleOnHover` enabled, this will be true if the
|
|
13
|
+
* feature is currently being hovered over.
|
|
14
|
+
* @returns {Style}
|
|
5
15
|
*/
|
|
6
16
|
/**
|
|
7
17
|
* Class representing a style.
|
|
@@ -13,14 +23,35 @@
|
|
|
13
23
|
* @property {number} properties.pointRadius - Radius of drawn "Point"-type geometries, if present
|
|
14
24
|
*/
|
|
15
25
|
export class Style {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @param {Object} [properties]
|
|
28
|
+
* @param {Stroke} [properties.stroke]
|
|
29
|
+
* @param {Fill} [properties.fill]
|
|
30
|
+
* @param {Text} [properties.text]
|
|
31
|
+
* @param {number} [properties.pointRadius]
|
|
32
|
+
*/
|
|
33
|
+
constructor(properties?: {
|
|
34
|
+
stroke?: Stroke;
|
|
35
|
+
fill?: Fill;
|
|
36
|
+
text?: Text;
|
|
37
|
+
pointRadius?: number;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* @type {Stroke}
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
public stroke: Stroke;
|
|
44
|
+
/**
|
|
45
|
+
* @type {Fill}
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
public fill: Fill;
|
|
49
|
+
/**
|
|
50
|
+
* @type {Text}
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
public text: Text;
|
|
54
|
+
pointRadius: number;
|
|
21
55
|
clone(): Style;
|
|
22
56
|
}
|
|
23
|
-
|
|
24
|
-
* A function that takes a {@link import ("../Feature").Feature} and returns a {@link Style}
|
|
25
|
-
*/
|
|
26
|
-
export type StyleFunction = (arg0: import('../Feature').Feature) => (Style);
|
|
57
|
+
export type StyleFunction = (feature: import('../Feature').Feature, zoom: number, isHovering?: boolean) => Style;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
class Style {
|
|
2
|
+
/**
|
|
3
|
+
* @param {Object} [properties]
|
|
4
|
+
* @param {Stroke} [properties.stroke]
|
|
5
|
+
* @param {Fill} [properties.fill]
|
|
6
|
+
* @param {Text} [properties.text]
|
|
7
|
+
* @param {number} [properties.pointRadius]
|
|
8
|
+
*/
|
|
2
9
|
constructor(properties) {
|
|
3
10
|
this.stroke = properties == null ? void 0 : properties.stroke;
|
|
4
11
|
this.fill = properties == null ? void 0 : properties.fill;
|