@abi-software/flatmap-viewer 2.5.8 → 2.6.0-a.1
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/README.rst +2 -2
- package/package.json +7 -20
- package/src/controls/annotation.js +21 -7
- package/src/interactions.js +93 -56
- package/src/layers/filter.ts +187 -0
- package/src/layers/index.js +7 -0
- package/src/main.js +3 -0
package/README.rst
CHANGED
|
@@ -26,7 +26,7 @@ Running
|
|
|
26
26
|
|
|
27
27
|
::
|
|
28
28
|
|
|
29
|
-
$ npm
|
|
29
|
+
$ npm run dev
|
|
30
30
|
|
|
31
31
|
Maps can then be viewed at http://localhost:3000
|
|
32
32
|
|
|
@@ -38,7 +38,7 @@ The map server endpoint is specified as ``MAP_ENDPOINT`` in ``src/main.js``. It
|
|
|
38
38
|
Package Installation
|
|
39
39
|
====================
|
|
40
40
|
|
|
41
|
-
* ``npm install @abi-software/flatmap-viewer@2.5.
|
|
41
|
+
* ``npm install @abi-software/flatmap-viewer@2.5.9``
|
|
42
42
|
|
|
43
43
|
Documentation
|
|
44
44
|
-------------
|
package/package.json
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abi-software/flatmap-viewer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0-a.1",
|
|
4
4
|
"description": "Flatmap viewer using Maplibre GL",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/AnatomicMaps/flatmap-viewer.git"
|
|
8
8
|
},
|
|
9
9
|
"main": "src/main.js",
|
|
10
|
+
"type": "module",
|
|
10
11
|
"files": [
|
|
11
12
|
"src",
|
|
12
13
|
"static"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
16
|
-
"
|
|
17
|
-
"build": "
|
|
17
|
+
"dev": "vite serve app --port 3000",
|
|
18
|
+
"build": "tsc && vite build",
|
|
19
|
+
"preview": "vite preview",
|
|
18
20
|
"docs": "cd docs; poetry run make html"
|
|
19
21
|
},
|
|
20
22
|
"author": "David Brooks",
|
|
21
23
|
"license": "MIT",
|
|
22
24
|
"dependencies": {
|
|
23
|
-
"@babel/runtime": "^7.10.4",
|
|
24
25
|
"@deck.gl/core": "^8.9.33",
|
|
25
26
|
"@deck.gl/layers": "^8.9.33",
|
|
26
27
|
"@deck.gl/mapbox": "^8.9.33",
|
|
@@ -38,22 +39,8 @@
|
|
|
38
39
|
"polylabel": "^1.1.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@babel/core": "^7.5.5",
|
|
42
|
-
"@babel/plugin-transform-runtime": "^7.5.5",
|
|
43
|
-
"@babel/preset-env": "^7.10.4",
|
|
44
|
-
"babel-loader": "^8.1.0",
|
|
45
|
-
"browser-sync": "^2.26.7",
|
|
46
|
-
"bs-fullscreen-message": "^1.1.0",
|
|
47
|
-
"clean-webpack-plugin": "^3.0.0",
|
|
48
|
-
"css-loader": "^6.7.3",
|
|
49
42
|
"eslint": "^8.7.0",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"strip-ansi": "^7.0.1",
|
|
53
|
-
"style-loader": "^3.3.2",
|
|
54
|
-
"webpack": "^5.16.0",
|
|
55
|
-
"webpack-cli": "^4.4.0",
|
|
56
|
-
"webpack-dev-middleware": "^4.1.0",
|
|
57
|
-
"webpack-node-externals": "^1.7.2"
|
|
43
|
+
"typescript": "^5.2.2",
|
|
44
|
+
"vite": "^5.1.4"
|
|
58
45
|
}
|
|
59
46
|
}
|
|
@@ -41,10 +41,9 @@ limitations under the License.
|
|
|
41
41
|
|
|
42
42
|
//==============================================================================
|
|
43
43
|
|
|
44
|
-
import MapboxDraw from "@mapbox/mapbox-gl-draw"
|
|
44
|
+
import MapboxDraw from "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw-unminified.js"
|
|
45
45
|
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
|
|
46
46
|
|
|
47
|
-
|
|
48
47
|
//==============================================================================
|
|
49
48
|
|
|
50
49
|
const drawStyleIds = MapboxDraw.lib.theme.map(s => s.id)
|
|
@@ -78,6 +77,7 @@ export class AnnotationDrawControl
|
|
|
78
77
|
keybindings: true
|
|
79
78
|
})
|
|
80
79
|
this.__map = null
|
|
80
|
+
this.__inDrawing = false
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
onAdd(map)
|
|
@@ -96,7 +96,7 @@ export class AnnotationDrawControl
|
|
|
96
96
|
e.preventDefault();
|
|
97
97
|
}
|
|
98
98
|
}, false)
|
|
99
|
-
map.on('draw.modechange', this.
|
|
99
|
+
map.on('draw.modechange', this.modeChangedEvent.bind(this))
|
|
100
100
|
map.on('draw.create', this.createdFeature.bind(this))
|
|
101
101
|
map.on('draw.delete', this.deletedFeature.bind(this))
|
|
102
102
|
map.on('draw.update', this.updatedFeature.bind(this))
|
|
@@ -197,13 +197,20 @@ export class AnnotationDrawControl
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
modeChangedEvent(event)
|
|
201
|
+
//=====================
|
|
202
202
|
{
|
|
203
203
|
// Used as a flag to indicate the feature mode
|
|
204
|
+
this.__inDrawing = (event.mode.startsWith('draw'))
|
|
204
205
|
this.#sendEvent('modeChanged', event)
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
inDrawingMode()
|
|
209
|
+
//=============
|
|
210
|
+
{
|
|
211
|
+
return this.__inDrawing
|
|
212
|
+
}
|
|
213
|
+
|
|
207
214
|
commitEvent(event)
|
|
208
215
|
//================
|
|
209
216
|
{
|
|
@@ -214,6 +221,8 @@ export class AnnotationDrawControl
|
|
|
214
221
|
this.__committedFeatures.set(feature.id, feature)
|
|
215
222
|
}
|
|
216
223
|
this.__uncommittedFeatureIds.delete(feature.id)
|
|
224
|
+
this.__flatmap.showPopup(feature.id, '<div>committed</div>')
|
|
225
|
+
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
abortEvent(event)
|
|
@@ -261,10 +270,15 @@ export class AnnotationDrawControl
|
|
|
261
270
|
this.__uncommittedFeatureIds.delete(ids[0])
|
|
262
271
|
}
|
|
263
272
|
|
|
264
|
-
|
|
273
|
+
getFeature(featureId)
|
|
274
|
+
//===================
|
|
275
|
+
{
|
|
276
|
+
return this.__draw.get(featureId) || null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
selectFeature(featureId)
|
|
265
280
|
//======================
|
|
266
281
|
{
|
|
267
|
-
return this.__draw.get(feature.id) || null
|
|
268
282
|
}
|
|
269
283
|
}
|
|
270
284
|
|
package/src/interactions.js
CHANGED
|
@@ -102,6 +102,8 @@ function labelPosition(feature)
|
|
|
102
102
|
if (feature.geometry.type === 'Point') {
|
|
103
103
|
return feature.geometry.coordinates
|
|
104
104
|
}
|
|
105
|
+
// LineString?
|
|
106
|
+
// Multi geometries??
|
|
105
107
|
const polygon = feature.geometry.coordinates;
|
|
106
108
|
// Rough heuristic. Area is in km^2; below appears to be good enough.
|
|
107
109
|
const precision = ('area' in feature.properties)
|
|
@@ -266,7 +268,6 @@ export class UserInteractions
|
|
|
266
268
|
this._map.addControl(this.#annotationDrawControl)
|
|
267
269
|
|
|
268
270
|
// Handle mouse events
|
|
269
|
-
|
|
270
271
|
this._map.on('click', this.clickEvent_.bind(this));
|
|
271
272
|
this._map.on('touchend', this.clickEvent_.bind(this));
|
|
272
273
|
this._map.on('mousemove', this.mouseMoveEvent_.bind(this));
|
|
@@ -333,6 +334,14 @@ export class UserInteractions
|
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
|
|
337
|
+
inDrawingAnnotationMode()
|
|
338
|
+
//=======================
|
|
339
|
+
{
|
|
340
|
+
if (this.#annotationDrawControl) {
|
|
341
|
+
return this.#annotationDrawControl.inDrawingMode()
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
336
345
|
commitAnnotationEvent(event)
|
|
337
346
|
//==========================
|
|
338
347
|
{
|
|
@@ -377,7 +386,7 @@ export class UserInteractions
|
|
|
377
386
|
//=======================================
|
|
378
387
|
{
|
|
379
388
|
if (this.#annotationDrawControl) {
|
|
380
|
-
return this.#annotationDrawControl.
|
|
389
|
+
return this.#annotationDrawControl.getFeature(feature.id)
|
|
381
390
|
}
|
|
382
391
|
}
|
|
383
392
|
|
|
@@ -457,21 +466,37 @@ export class UserInteractions
|
|
|
457
466
|
this.__systemsManager.enable(systemId, enable);
|
|
458
467
|
}
|
|
459
468
|
|
|
469
|
+
#getAnnotationProperties(featureId)
|
|
470
|
+
//=================================
|
|
471
|
+
{
|
|
472
|
+
const properties = this._flatmap.annotation(featureId)
|
|
473
|
+
if (properties) {
|
|
474
|
+
return properties
|
|
475
|
+
} else if (this.#annotationDrawControl) {
|
|
476
|
+
const drawnFeature = this.#annotationDrawControl.getFeature(featureId)
|
|
477
|
+
if (drawnFeature) {
|
|
478
|
+
drawnFeature.properties.user_drawn = true
|
|
479
|
+
return drawnFeature.properties
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return null
|
|
483
|
+
}
|
|
484
|
+
|
|
460
485
|
mapFeature(featureId)
|
|
461
486
|
//===================
|
|
462
487
|
{
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
488
|
+
const properties = this.#getAnnotationProperties(featureId);
|
|
489
|
+
if (properties && !properties.user_drawn) {
|
|
465
490
|
return {
|
|
466
491
|
id: featureId,
|
|
467
492
|
source: VECTOR_TILES_SOURCE,
|
|
468
493
|
sourceLayer: (this._flatmap.options.separateLayers
|
|
469
|
-
? `${
|
|
470
|
-
:
|
|
471
|
-
children:
|
|
494
|
+
? `${properties['layer']}_${properties['tile-layer']}`
|
|
495
|
+
: properties['tile-layer']).replaceAll('/', '_'),
|
|
496
|
+
children: properties.children || []
|
|
472
497
|
};
|
|
473
498
|
}
|
|
474
|
-
return
|
|
499
|
+
return null;
|
|
475
500
|
}
|
|
476
501
|
|
|
477
502
|
#getFeatureState(feature)
|
|
@@ -501,7 +526,7 @@ export class UserInteractions
|
|
|
501
526
|
enableMapFeature(feature, enable=true)
|
|
502
527
|
//====================================
|
|
503
528
|
{
|
|
504
|
-
if (feature
|
|
529
|
+
if (feature) {
|
|
505
530
|
const state = this.#getFeatureState(feature);
|
|
506
531
|
if ('hidden' in state) {
|
|
507
532
|
if (enable) {
|
|
@@ -534,7 +559,7 @@ export class UserInteractions
|
|
|
534
559
|
//============================================================
|
|
535
560
|
{
|
|
536
561
|
const feature = this.mapFeature(featureId);
|
|
537
|
-
if (feature
|
|
562
|
+
if (feature) {
|
|
538
563
|
this.enableFeature(featureId, enable, force);
|
|
539
564
|
for (const childFeatureId of feature.children) {
|
|
540
565
|
this.enableFeatureWithChildren(childFeatureId, enable, force);
|
|
@@ -546,7 +571,7 @@ export class UserInteractions
|
|
|
546
571
|
//===========================================
|
|
547
572
|
{
|
|
548
573
|
const markerId = this.__markerIdByFeatureId.get(+featureId);
|
|
549
|
-
if (markerId
|
|
574
|
+
if (markerId) {
|
|
550
575
|
const markerDiv = document.getElementById(`marker-${markerId}`);
|
|
551
576
|
if (markerDiv) {
|
|
552
577
|
markerDiv.style.visibility = enable ? 'visible' : 'hidden';
|
|
@@ -559,7 +584,7 @@ export class UserInteractions
|
|
|
559
584
|
{
|
|
560
585
|
if (feature.id) {
|
|
561
586
|
const state = this.#getFeatureState(feature);
|
|
562
|
-
return (state
|
|
587
|
+
return (state
|
|
563
588
|
&& (!('hidden' in state) || !state.hidden));
|
|
564
589
|
}
|
|
565
590
|
return DRAW_ANNOTATION_LAYERS.includes(feature.layer.id)
|
|
@@ -574,12 +599,12 @@ export class UserInteractions
|
|
|
574
599
|
selectFeature(featureId, dim=true)
|
|
575
600
|
//================================
|
|
576
601
|
{
|
|
577
|
-
const
|
|
578
|
-
if (
|
|
602
|
+
const properties = this.#getAnnotationProperties(featureId);
|
|
603
|
+
if (properties && 'sckan' in properties) {
|
|
579
604
|
const sckanState = this._layerManager.sckanState;
|
|
580
605
|
if (sckanState === 'none'
|
|
581
|
-
|| sckanState === 'valid' && !
|
|
582
|
-
|| sckanState === 'invalid' &&
|
|
606
|
+
|| sckanState === 'valid' && !properties.sckan
|
|
607
|
+
|| sckanState === 'invalid' && properties.sckan) {
|
|
583
608
|
return false;
|
|
584
609
|
}
|
|
585
610
|
}
|
|
@@ -589,11 +614,15 @@ export class UserInteractions
|
|
|
589
614
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
590
615
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
591
616
|
result = true;
|
|
617
|
+
} else if (properties.user_drawn) {
|
|
618
|
+
if (this.#annotationDrawControl) {
|
|
619
|
+
this.#annotationDrawControl.selectFeature(featureId, true)
|
|
620
|
+
}
|
|
592
621
|
} else {
|
|
593
622
|
const feature = this.mapFeature(featureId);
|
|
594
|
-
if (feature
|
|
623
|
+
if (feature) {
|
|
595
624
|
const state = this.#getFeatureState(feature);
|
|
596
|
-
if (state
|
|
625
|
+
if (state && (!('hidden' in state) || !state.hidden)) {
|
|
597
626
|
this.#setFeatureState(feature, { selected: true });
|
|
598
627
|
this._selectedFeatureIds.set(featureId, 1);
|
|
599
628
|
result = true;
|
|
@@ -616,7 +645,7 @@ export class UserInteractions
|
|
|
616
645
|
this._selectedFeatureIds.set(featureId, references - 1);
|
|
617
646
|
} else {
|
|
618
647
|
const feature = this.mapFeature(featureId);
|
|
619
|
-
if (feature
|
|
648
|
+
if (feature) {
|
|
620
649
|
this.#removeFeatureState(feature, 'selected');
|
|
621
650
|
this._selectedFeatureIds.delete(+featureId);
|
|
622
651
|
}
|
|
@@ -632,7 +661,7 @@ export class UserInteractions
|
|
|
632
661
|
{
|
|
633
662
|
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
634
663
|
const feature = this.mapFeature(featureId);
|
|
635
|
-
if (feature
|
|
664
|
+
if (feature) {
|
|
636
665
|
this.#removeFeatureState(feature, 'selected');
|
|
637
666
|
}
|
|
638
667
|
}
|
|
@@ -643,7 +672,7 @@ export class UserInteractions
|
|
|
643
672
|
activateFeature(feature)
|
|
644
673
|
//======================
|
|
645
674
|
{
|
|
646
|
-
if (feature
|
|
675
|
+
if (feature) {
|
|
647
676
|
this.#setFeatureState(feature, { active: true });
|
|
648
677
|
this._activeFeatures.add(feature);
|
|
649
678
|
}
|
|
@@ -729,7 +758,7 @@ export class UserInteractions
|
|
|
729
758
|
if (featureIds.length) {
|
|
730
759
|
this.unselectFeatures();
|
|
731
760
|
for (const featureId of featureIds) {
|
|
732
|
-
const annotation = this
|
|
761
|
+
const annotation = this.#getAnnotationProperties(featureId);
|
|
733
762
|
if (annotation) {
|
|
734
763
|
if (this.selectFeature(featureId)) {
|
|
735
764
|
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
@@ -777,14 +806,14 @@ export class UserInteractions
|
|
|
777
806
|
padding.lng -= bbox[0];
|
|
778
807
|
padding.lat = bbox[3] - padding.lat;
|
|
779
808
|
for (const featureId of featureIds) {
|
|
780
|
-
const annotation = this
|
|
809
|
+
const annotation = this.#getAnnotationProperties(featureId);
|
|
781
810
|
if (annotation) {
|
|
782
811
|
if (this.selectFeature(featureId)) {
|
|
783
812
|
bbox = expandBounds(bbox, annotation.bounds, padding);
|
|
784
813
|
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
785
814
|
for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
|
|
786
815
|
if (this.selectFeature(pathFeatureId)) {
|
|
787
|
-
const pathAnnotation = this
|
|
816
|
+
const pathAnnotation = this.#getAnnotationProperties(pathFeatureId)
|
|
788
817
|
bbox = expandBounds(bbox, pathAnnotation.bounds, padding);
|
|
789
818
|
}
|
|
790
819
|
}
|
|
@@ -804,12 +833,11 @@ export class UserInteractions
|
|
|
804
833
|
showPopup(featureId, content, options={})
|
|
805
834
|
//=======================================
|
|
806
835
|
{
|
|
807
|
-
const
|
|
836
|
+
const properties = this.#getAnnotationProperties(featureId);
|
|
808
837
|
const drawn = options && options.annotationFeatureGeometry;
|
|
809
|
-
if (
|
|
838
|
+
if (properties || drawn) { // The feature exists or it is a drawn annotation
|
|
810
839
|
|
|
811
840
|
// Remove any existing popup
|
|
812
|
-
|
|
813
841
|
if (this._currentPopup) {
|
|
814
842
|
if (options && options.preserveSelection) {
|
|
815
843
|
this._currentPopup.options.preserveSelection = options.preserveSelection;
|
|
@@ -818,7 +846,6 @@ export class UserInteractions
|
|
|
818
846
|
}
|
|
819
847
|
|
|
820
848
|
// Clear selection if we are not preserving it
|
|
821
|
-
|
|
822
849
|
if (options && options.preserveSelection) {
|
|
823
850
|
delete options.preserveSelection; // Don't pass to onClose()
|
|
824
851
|
} else { // via the popup's options
|
|
@@ -826,10 +853,9 @@ export class UserInteractions
|
|
|
826
853
|
}
|
|
827
854
|
|
|
828
855
|
// Select the feature
|
|
829
|
-
|
|
830
856
|
this.selectFeature(featureId);
|
|
831
857
|
|
|
832
|
-
// Find the pop-up's
|
|
858
|
+
// Find the pop-up's position
|
|
833
859
|
|
|
834
860
|
let location = null;
|
|
835
861
|
if ('positionAtLastClick' in options
|
|
@@ -842,7 +868,7 @@ export class UserInteractions
|
|
|
842
868
|
location = options.annotationFeatureGeometry;
|
|
843
869
|
} else {
|
|
844
870
|
// Position popup at the feature's 'centre'
|
|
845
|
-
location = this.__markerPosition(featureId,
|
|
871
|
+
location = this.__markerPosition(featureId, properties);
|
|
846
872
|
}
|
|
847
873
|
|
|
848
874
|
// Make sure the feature is on screen
|
|
@@ -858,10 +884,11 @@ export class UserInteractions
|
|
|
858
884
|
}
|
|
859
885
|
this._currentPopup.setLngLat(location);
|
|
860
886
|
if (typeof content === 'object') {
|
|
861
|
-
this._currentPopup.setDOMContent(content)
|
|
887
|
+
this._currentPopup.setDOMContent(content)
|
|
862
888
|
} else {
|
|
863
|
-
this._currentPopup.setText(content)
|
|
889
|
+
this._currentPopup.setText(content)
|
|
864
890
|
}
|
|
891
|
+
this._currentPopup.addTo(this._map)
|
|
865
892
|
}
|
|
866
893
|
}
|
|
867
894
|
|
|
@@ -1139,7 +1166,7 @@ export class UserInteractions
|
|
|
1139
1166
|
selectionEvent_(event, feature)
|
|
1140
1167
|
//=============================
|
|
1141
1168
|
{
|
|
1142
|
-
if (feature
|
|
1169
|
+
if (feature) {
|
|
1143
1170
|
const clickedFeatureId = +feature.id;
|
|
1144
1171
|
const dim = !('properties' in feature
|
|
1145
1172
|
&& 'kind' in feature.properties
|
|
@@ -1185,22 +1212,25 @@ export class UserInteractions
|
|
|
1185
1212
|
this.unselectFeatures();
|
|
1186
1213
|
return;
|
|
1187
1214
|
}
|
|
1188
|
-
const
|
|
1189
|
-
const clickedDrawnFeature = clickedFeatures.filter((f)
|
|
1215
|
+
const inDrawing = this.inDrawingAnnotationMode()
|
|
1216
|
+
const clickedDrawnFeature = clickedFeatures.filter((f) => !f.id)[0];
|
|
1217
|
+
const clickedFeature = clickedFeatures.filter((f) => f.id)[0];
|
|
1190
1218
|
this.selectionEvent_(event.originalEvent, clickedFeature);
|
|
1191
1219
|
if (this._modal) {
|
|
1192
1220
|
// Remove tooltip, reset active features, etc
|
|
1193
1221
|
this.__resetFeatureDisplay();
|
|
1194
1222
|
this.unselectFeatures();
|
|
1195
1223
|
this.__clearModal();
|
|
1196
|
-
} else if (
|
|
1224
|
+
} else if (clickedDrawnFeature && !inDrawing) {
|
|
1225
|
+
// When feature and drawn feature are coinciding, click on annotation layer by default
|
|
1226
|
+
// While in drawing, DISABLE 'click' event on annotation layer
|
|
1227
|
+
this.__featureEvent('click', clickedDrawnFeature);
|
|
1228
|
+
} else if (clickedFeature) {
|
|
1197
1229
|
this.__lastClickLngLat = event.lngLat;
|
|
1198
1230
|
this.__featureEvent('click', clickedFeature);
|
|
1199
1231
|
if ('properties' in clickedFeature && 'hyperlink' in clickedFeature.properties) {
|
|
1200
1232
|
window.open(clickedFeature.properties.hyperlink, '_blank');
|
|
1201
1233
|
}
|
|
1202
|
-
} else if (clickedDrawnFeature !== undefined) {
|
|
1203
|
-
this.__featureEvent('click', clickedDrawnFeature);
|
|
1204
1234
|
}
|
|
1205
1235
|
}
|
|
1206
1236
|
|
|
@@ -1294,26 +1324,33 @@ export class UserInteractions
|
|
|
1294
1324
|
|
|
1295
1325
|
// Marker handling
|
|
1296
1326
|
|
|
1297
|
-
__markerPosition(featureId,
|
|
1327
|
+
__markerPosition(featureId, properties)
|
|
1298
1328
|
{
|
|
1299
1329
|
if (this.__markerPositions.has(featureId)) {
|
|
1300
1330
|
return this.__markerPositions.get(featureId);
|
|
1301
1331
|
}
|
|
1302
|
-
let position =
|
|
1303
|
-
if (position
|
|
1332
|
+
let position = properties.markerPosition || properties.centroid || null;
|
|
1333
|
+
if (!position) {
|
|
1304
1334
|
// Find where to place a label or popup on a feature
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1335
|
+
if (properties.user_drawn) {
|
|
1336
|
+
if (this.#annotationDrawControl) {
|
|
1337
|
+
const feature = this.#annotationDrawControl.getFeature(featureId)
|
|
1338
|
+
position = labelPosition(feature);
|
|
1339
|
+
}
|
|
1340
|
+
} else {
|
|
1341
|
+
const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
|
|
1342
|
+
'sourceLayer': this._flatmap.options.separateLayers
|
|
1343
|
+
? `${properties['layer']}_${properties['tile-layer']}`
|
|
1344
|
+
: properties['tile-layer'],
|
|
1345
|
+
'filter': [
|
|
1346
|
+
'all',
|
|
1347
|
+
[ '==', ['id'], parseInt(featureId) ],
|
|
1348
|
+
[ '==', ['geometry-type'], 'Polygon' ]
|
|
1349
|
+
]
|
|
1350
|
+
});
|
|
1351
|
+
if (features.length > 0) {
|
|
1352
|
+
position = labelPosition(features[0]);
|
|
1353
|
+
}
|
|
1317
1354
|
}
|
|
1318
1355
|
}
|
|
1319
1356
|
this.__markerPositions.set(featureId, position);
|
|
@@ -1327,7 +1364,7 @@ export class UserInteractions
|
|
|
1327
1364
|
let markerId = -1;
|
|
1328
1365
|
|
|
1329
1366
|
for (const featureId of featureIds) {
|
|
1330
|
-
const annotation = this
|
|
1367
|
+
const annotation = this.#getAnnotationProperties(featureId);
|
|
1331
1368
|
if (!('markerPosition' in annotation) && !annotation.geometry.includes('Polygon')) {
|
|
1332
1369
|
continue;
|
|
1333
1370
|
}
|
|
@@ -1443,7 +1480,7 @@ export class UserInteractions
|
|
|
1443
1480
|
const annotation = this.__annotationByMarkerId.get(markerId);
|
|
1444
1481
|
// The marker's feature
|
|
1445
1482
|
const feature = this.mapFeature(annotation.featureId);
|
|
1446
|
-
if (feature
|
|
1483
|
+
if (feature) {
|
|
1447
1484
|
if (event.type === 'mouseenter') {
|
|
1448
1485
|
// Highlight on mouse enter
|
|
1449
1486
|
this.resetActiveFeatures_();
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
|
|
3
|
+
Flatmap viewer and annotation tool
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2019 - 2024 David Brooks
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
|
19
|
+
******************************************************************************/
|
|
20
|
+
|
|
21
|
+
type FilterExpression = Record<string, any>
|
|
22
|
+
|
|
23
|
+
//==============================================================================
|
|
24
|
+
|
|
25
|
+
export class FeatureFilter
|
|
26
|
+
{
|
|
27
|
+
#filter: FilterExpression
|
|
28
|
+
|
|
29
|
+
constructor(filter: FilterExpression)
|
|
30
|
+
{
|
|
31
|
+
this.#filter = filter
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
makeStyleFilter()
|
|
35
|
+
//===============
|
|
36
|
+
{
|
|
37
|
+
return this.#makeStyleFilter(this.#filter)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#makeStyleFilter(filter: FilterExpression): Array<any>
|
|
41
|
+
//====================================================
|
|
42
|
+
{
|
|
43
|
+
// We expect an object, so check and warn...
|
|
44
|
+
if (!filter || filter.constructor !== Object) {
|
|
45
|
+
console.warn(`makeFilter: Invalid filter expression: ${filter}`)
|
|
46
|
+
return []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const styleFilter = []
|
|
50
|
+
for (const [key, expr] of Object.entries(filter)) {
|
|
51
|
+
if (key === 'AND' || key === 'OR') {
|
|
52
|
+
if (Array.isArray(expr) && expr.length >= 2) {
|
|
53
|
+
styleFilter.push((key === 'AND') ? 'all' : 'any',
|
|
54
|
+
...expr.map(e => this.#makeStyleFilter(e)))
|
|
55
|
+
} else {
|
|
56
|
+
console.warn(`makeFilter: Invalid ${key} operands: ${expr}`)
|
|
57
|
+
}
|
|
58
|
+
} else if (key === 'HAS') {
|
|
59
|
+
styleFilter.push('has', expr)
|
|
60
|
+
} else if (key === 'NOT') {
|
|
61
|
+
const filterExpr = this.#makeStyleFilter(expr)
|
|
62
|
+
if (filterExpr.length === 2 && ['has', '!has'].includes(filterExpr[0])) {
|
|
63
|
+
if (filterExpr[0] === 'has') {
|
|
64
|
+
styleFilter.push('!has', filterExpr[1])
|
|
65
|
+
} else {
|
|
66
|
+
styleFilter.push('has', filterExpr[1])
|
|
67
|
+
}
|
|
68
|
+
} else if (filterExpr.length === 3 && ['==', '!='].includes(filterExpr[0])) {
|
|
69
|
+
if (filterExpr[0] === '==') {
|
|
70
|
+
styleFilter.push('!=', filterExpr[1], filterExpr[2])
|
|
71
|
+
} else {
|
|
72
|
+
styleFilter.push('==', filterExpr[1], filterExpr[2])
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
styleFilter.push('!', filterExpr)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
if (Array.isArray(expr)) {
|
|
79
|
+
styleFilter.push('any', ...expr.map(e => ['==', key, e]))
|
|
80
|
+
} else {
|
|
81
|
+
styleFilter.push('==', key, expr)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return styleFilter
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//==============================================================================
|
|
91
|
+
|
|
92
|
+
function testFilter(f: FilterExpression)
|
|
93
|
+
//======================================
|
|
94
|
+
{
|
|
95
|
+
const featureFilter = new FeatureFilter(f)
|
|
96
|
+
console.log(f, '--->', featureFilter.makeStyleFilter())
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function testFilters()
|
|
100
|
+
//===========================
|
|
101
|
+
{
|
|
102
|
+
/*
|
|
103
|
+
{ HAS: 'prop' } ---> [ 'has', 'prop' ]
|
|
104
|
+
{ prop: 1 } ---> [ '==', 'prop', 1 ]
|
|
105
|
+
{ NOT: { prop: 1 } } ---> [ '!=', 'prop', 1 ]
|
|
106
|
+
{ NOT: { prop: [ 1, 2 ] } } ---> [ '!', [ 'any', [ '==', 'prop', 1 ], [ '==', 'prop', 2 ] ] ]
|
|
107
|
+
{ OR: [ { prop1: 10 }, { prop2: 11 } ] } ---> [ 'any', [ '==', 'prop1', 10 ], [ '==', 'prop2', 11 ] ]
|
|
108
|
+
{ AND: [ { prop1: 10 }, { prop2: 11 } ] } ---> [ 'all', [ '==', 'prop1', 10 ], [ '==', 'prop2', 11 ] ]
|
|
109
|
+
{ OR: [ { AND: [Array] }, { AND: [Array] } ] } ---> [
|
|
110
|
+
'any',
|
|
111
|
+
[ 'all', [ '!=', 'prop1', 10 ], [ '==', 'prop2', 11 ] ],
|
|
112
|
+
[ 'all', [ '==', 'prop3', 10 ], [ '==', 'prop4', 11 ] ]
|
|
113
|
+
]
|
|
114
|
+
{ NOT: { OR: [ [Object], [Object] ] } } ---> [
|
|
115
|
+
'!',
|
|
116
|
+
[ 'any', [ 'all', [Array], [Array] ], [ 'all', [Array], [Array] ] ]
|
|
117
|
+
]
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
testFilter({
|
|
121
|
+
"HAS": "prop"
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
testFilter({
|
|
125
|
+
"prop": 1
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
testFilter({
|
|
129
|
+
"NOT": {
|
|
130
|
+
"prop": 1
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
testFilter({
|
|
135
|
+
"NOT": {
|
|
136
|
+
"prop": [1, 2]
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
testFilter({
|
|
141
|
+
"OR": [
|
|
142
|
+
{"prop1": 10},
|
|
143
|
+
{"prop2": 11}
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
testFilter({
|
|
148
|
+
"AND": [
|
|
149
|
+
{"prop1": 10},
|
|
150
|
+
{"prop2": 11}
|
|
151
|
+
]
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
testFilter({
|
|
155
|
+
"OR": [{
|
|
156
|
+
"AND": [
|
|
157
|
+
{ "NOT": {"prop1": 10}},
|
|
158
|
+
{"prop2": 11}
|
|
159
|
+
]}, {
|
|
160
|
+
"AND": [
|
|
161
|
+
{"prop3": 10},
|
|
162
|
+
{"prop4": 11}
|
|
163
|
+
]}
|
|
164
|
+
]
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
testFilter({
|
|
168
|
+
"NOT": {
|
|
169
|
+
"OR": [{
|
|
170
|
+
"AND": [
|
|
171
|
+
{"prop1": 10},
|
|
172
|
+
{"prop2": 11}
|
|
173
|
+
]}, {
|
|
174
|
+
"AND": [
|
|
175
|
+
{"prop3": 10},
|
|
176
|
+
{"prop4": 11}
|
|
177
|
+
]}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
//==============================================================================
|
|
184
|
+
|
|
185
|
+
//testFilters()
|
|
186
|
+
|
|
187
|
+
//==============================================================================
|
package/src/layers/index.js
CHANGED
|
@@ -22,6 +22,8 @@ limitations under the License.
|
|
|
22
22
|
|
|
23
23
|
//==============================================================================
|
|
24
24
|
|
|
25
|
+
import {FeatureFilter} from './filter'
|
|
26
|
+
|
|
25
27
|
import {PATHWAYS_LAYER} from '../pathways.js';
|
|
26
28
|
import * as utils from '../utils.js';
|
|
27
29
|
|
|
@@ -44,6 +46,11 @@ class MapStylingLayers
|
|
|
44
46
|
this.__layers = [];
|
|
45
47
|
this.__layerOptions = options;
|
|
46
48
|
this.__separateLayers = flatmap.options.separateLayers;
|
|
49
|
+
|
|
50
|
+
const f = new FeatureFilter({
|
|
51
|
+
"HAS": "prop"
|
|
52
|
+
})
|
|
53
|
+
console.log(f.makeStyleFilter())
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
get id()
|
package/src/main.js
CHANGED
|
@@ -64,6 +64,9 @@ class DrawControl
|
|
|
64
64
|
//================
|
|
65
65
|
{
|
|
66
66
|
console.log(event)
|
|
67
|
+
|
|
68
|
+
this._flatmap.showPopup(event.feature.id, '<div>event</div>')
|
|
69
|
+
|
|
67
70
|
if (this._idField) {
|
|
68
71
|
this._idField.innerText = `Annotation ${event.type}, Id: ${event.feature.id}`
|
|
69
72
|
this._lastEvent = event
|