@abi-software/flatmap-viewer 2.5.9 → 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 +1 -1
- package/package.json +7 -20
- package/src/controls/annotation.js +10 -4
- package/src/interactions.js +77 -51
- package/src/layers/filter.ts +187 -0
- package/src/layers/index.js +7 -0
- package/src/main.js +3 -0
package/README.rst
CHANGED
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)
|
|
@@ -222,6 +221,8 @@ export class AnnotationDrawControl
|
|
|
222
221
|
this.__committedFeatures.set(feature.id, feature)
|
|
223
222
|
}
|
|
224
223
|
this.__uncommittedFeatureIds.delete(feature.id)
|
|
224
|
+
this.__flatmap.showPopup(feature.id, '<div>committed</div>')
|
|
225
|
+
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
abortEvent(event)
|
|
@@ -269,10 +270,15 @@ export class AnnotationDrawControl
|
|
|
269
270
|
this.__uncommittedFeatureIds.delete(ids[0])
|
|
270
271
|
}
|
|
271
272
|
|
|
272
|
-
|
|
273
|
+
getFeature(featureId)
|
|
274
|
+
//===================
|
|
275
|
+
{
|
|
276
|
+
return this.__draw.get(featureId) || null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
selectFeature(featureId)
|
|
273
280
|
//======================
|
|
274
281
|
{
|
|
275
|
-
return this.__draw.get(feature.id) || null
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
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));
|
|
@@ -385,7 +386,7 @@ export class UserInteractions
|
|
|
385
386
|
//=======================================
|
|
386
387
|
{
|
|
387
388
|
if (this.#annotationDrawControl) {
|
|
388
|
-
return this.#annotationDrawControl.
|
|
389
|
+
return this.#annotationDrawControl.getFeature(feature.id)
|
|
389
390
|
}
|
|
390
391
|
}
|
|
391
392
|
|
|
@@ -465,21 +466,37 @@ export class UserInteractions
|
|
|
465
466
|
this.__systemsManager.enable(systemId, enable);
|
|
466
467
|
}
|
|
467
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
|
+
|
|
468
485
|
mapFeature(featureId)
|
|
469
486
|
//===================
|
|
470
487
|
{
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
488
|
+
const properties = this.#getAnnotationProperties(featureId);
|
|
489
|
+
if (properties && !properties.user_drawn) {
|
|
473
490
|
return {
|
|
474
491
|
id: featureId,
|
|
475
492
|
source: VECTOR_TILES_SOURCE,
|
|
476
493
|
sourceLayer: (this._flatmap.options.separateLayers
|
|
477
|
-
? `${
|
|
478
|
-
:
|
|
479
|
-
children:
|
|
494
|
+
? `${properties['layer']}_${properties['tile-layer']}`
|
|
495
|
+
: properties['tile-layer']).replaceAll('/', '_'),
|
|
496
|
+
children: properties.children || []
|
|
480
497
|
};
|
|
481
498
|
}
|
|
482
|
-
return
|
|
499
|
+
return null;
|
|
483
500
|
}
|
|
484
501
|
|
|
485
502
|
#getFeatureState(feature)
|
|
@@ -509,7 +526,7 @@ export class UserInteractions
|
|
|
509
526
|
enableMapFeature(feature, enable=true)
|
|
510
527
|
//====================================
|
|
511
528
|
{
|
|
512
|
-
if (feature
|
|
529
|
+
if (feature) {
|
|
513
530
|
const state = this.#getFeatureState(feature);
|
|
514
531
|
if ('hidden' in state) {
|
|
515
532
|
if (enable) {
|
|
@@ -542,7 +559,7 @@ export class UserInteractions
|
|
|
542
559
|
//============================================================
|
|
543
560
|
{
|
|
544
561
|
const feature = this.mapFeature(featureId);
|
|
545
|
-
if (feature
|
|
562
|
+
if (feature) {
|
|
546
563
|
this.enableFeature(featureId, enable, force);
|
|
547
564
|
for (const childFeatureId of feature.children) {
|
|
548
565
|
this.enableFeatureWithChildren(childFeatureId, enable, force);
|
|
@@ -554,7 +571,7 @@ export class UserInteractions
|
|
|
554
571
|
//===========================================
|
|
555
572
|
{
|
|
556
573
|
const markerId = this.__markerIdByFeatureId.get(+featureId);
|
|
557
|
-
if (markerId
|
|
574
|
+
if (markerId) {
|
|
558
575
|
const markerDiv = document.getElementById(`marker-${markerId}`);
|
|
559
576
|
if (markerDiv) {
|
|
560
577
|
markerDiv.style.visibility = enable ? 'visible' : 'hidden';
|
|
@@ -567,7 +584,7 @@ export class UserInteractions
|
|
|
567
584
|
{
|
|
568
585
|
if (feature.id) {
|
|
569
586
|
const state = this.#getFeatureState(feature);
|
|
570
|
-
return (state
|
|
587
|
+
return (state
|
|
571
588
|
&& (!('hidden' in state) || !state.hidden));
|
|
572
589
|
}
|
|
573
590
|
return DRAW_ANNOTATION_LAYERS.includes(feature.layer.id)
|
|
@@ -582,12 +599,12 @@ export class UserInteractions
|
|
|
582
599
|
selectFeature(featureId, dim=true)
|
|
583
600
|
//================================
|
|
584
601
|
{
|
|
585
|
-
const
|
|
586
|
-
if (
|
|
602
|
+
const properties = this.#getAnnotationProperties(featureId);
|
|
603
|
+
if (properties && 'sckan' in properties) {
|
|
587
604
|
const sckanState = this._layerManager.sckanState;
|
|
588
605
|
if (sckanState === 'none'
|
|
589
|
-
|| sckanState === 'valid' && !
|
|
590
|
-
|| sckanState === 'invalid' &&
|
|
606
|
+
|| sckanState === 'valid' && !properties.sckan
|
|
607
|
+
|| sckanState === 'invalid' && properties.sckan) {
|
|
591
608
|
return false;
|
|
592
609
|
}
|
|
593
610
|
}
|
|
@@ -597,11 +614,15 @@ export class UserInteractions
|
|
|
597
614
|
if (this._selectedFeatureIds.has(featureId)) {
|
|
598
615
|
this._selectedFeatureIds.set(featureId, this._selectedFeatureIds.get(featureId) + 1);
|
|
599
616
|
result = true;
|
|
617
|
+
} else if (properties.user_drawn) {
|
|
618
|
+
if (this.#annotationDrawControl) {
|
|
619
|
+
this.#annotationDrawControl.selectFeature(featureId, true)
|
|
620
|
+
}
|
|
600
621
|
} else {
|
|
601
622
|
const feature = this.mapFeature(featureId);
|
|
602
|
-
if (feature
|
|
623
|
+
if (feature) {
|
|
603
624
|
const state = this.#getFeatureState(feature);
|
|
604
|
-
if (state
|
|
625
|
+
if (state && (!('hidden' in state) || !state.hidden)) {
|
|
605
626
|
this.#setFeatureState(feature, { selected: true });
|
|
606
627
|
this._selectedFeatureIds.set(featureId, 1);
|
|
607
628
|
result = true;
|
|
@@ -624,7 +645,7 @@ export class UserInteractions
|
|
|
624
645
|
this._selectedFeatureIds.set(featureId, references - 1);
|
|
625
646
|
} else {
|
|
626
647
|
const feature = this.mapFeature(featureId);
|
|
627
|
-
if (feature
|
|
648
|
+
if (feature) {
|
|
628
649
|
this.#removeFeatureState(feature, 'selected');
|
|
629
650
|
this._selectedFeatureIds.delete(+featureId);
|
|
630
651
|
}
|
|
@@ -640,7 +661,7 @@ export class UserInteractions
|
|
|
640
661
|
{
|
|
641
662
|
for (const featureId of this._selectedFeatureIds.keys()) {
|
|
642
663
|
const feature = this.mapFeature(featureId);
|
|
643
|
-
if (feature
|
|
664
|
+
if (feature) {
|
|
644
665
|
this.#removeFeatureState(feature, 'selected');
|
|
645
666
|
}
|
|
646
667
|
}
|
|
@@ -651,7 +672,7 @@ export class UserInteractions
|
|
|
651
672
|
activateFeature(feature)
|
|
652
673
|
//======================
|
|
653
674
|
{
|
|
654
|
-
if (feature
|
|
675
|
+
if (feature) {
|
|
655
676
|
this.#setFeatureState(feature, { active: true });
|
|
656
677
|
this._activeFeatures.add(feature);
|
|
657
678
|
}
|
|
@@ -737,7 +758,7 @@ export class UserInteractions
|
|
|
737
758
|
if (featureIds.length) {
|
|
738
759
|
this.unselectFeatures();
|
|
739
760
|
for (const featureId of featureIds) {
|
|
740
|
-
const annotation = this
|
|
761
|
+
const annotation = this.#getAnnotationProperties(featureId);
|
|
741
762
|
if (annotation) {
|
|
742
763
|
if (this.selectFeature(featureId)) {
|
|
743
764
|
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
@@ -785,14 +806,14 @@ export class UserInteractions
|
|
|
785
806
|
padding.lng -= bbox[0];
|
|
786
807
|
padding.lat = bbox[3] - padding.lat;
|
|
787
808
|
for (const featureId of featureIds) {
|
|
788
|
-
const annotation = this
|
|
809
|
+
const annotation = this.#getAnnotationProperties(featureId);
|
|
789
810
|
if (annotation) {
|
|
790
811
|
if (this.selectFeature(featureId)) {
|
|
791
812
|
bbox = expandBounds(bbox, annotation.bounds, padding);
|
|
792
813
|
if ('type' in annotation && annotation.type.startsWith('line')) {
|
|
793
814
|
for (const pathFeatureId of this.__pathManager.lineFeatureIds([featureId])) {
|
|
794
815
|
if (this.selectFeature(pathFeatureId)) {
|
|
795
|
-
const pathAnnotation = this
|
|
816
|
+
const pathAnnotation = this.#getAnnotationProperties(pathFeatureId)
|
|
796
817
|
bbox = expandBounds(bbox, pathAnnotation.bounds, padding);
|
|
797
818
|
}
|
|
798
819
|
}
|
|
@@ -812,12 +833,11 @@ export class UserInteractions
|
|
|
812
833
|
showPopup(featureId, content, options={})
|
|
813
834
|
//=======================================
|
|
814
835
|
{
|
|
815
|
-
const
|
|
836
|
+
const properties = this.#getAnnotationProperties(featureId);
|
|
816
837
|
const drawn = options && options.annotationFeatureGeometry;
|
|
817
|
-
if (
|
|
838
|
+
if (properties || drawn) { // The feature exists or it is a drawn annotation
|
|
818
839
|
|
|
819
840
|
// Remove any existing popup
|
|
820
|
-
|
|
821
841
|
if (this._currentPopup) {
|
|
822
842
|
if (options && options.preserveSelection) {
|
|
823
843
|
this._currentPopup.options.preserveSelection = options.preserveSelection;
|
|
@@ -826,7 +846,6 @@ export class UserInteractions
|
|
|
826
846
|
}
|
|
827
847
|
|
|
828
848
|
// Clear selection if we are not preserving it
|
|
829
|
-
|
|
830
849
|
if (options && options.preserveSelection) {
|
|
831
850
|
delete options.preserveSelection; // Don't pass to onClose()
|
|
832
851
|
} else { // via the popup's options
|
|
@@ -834,10 +853,9 @@ export class UserInteractions
|
|
|
834
853
|
}
|
|
835
854
|
|
|
836
855
|
// Select the feature
|
|
837
|
-
|
|
838
856
|
this.selectFeature(featureId);
|
|
839
857
|
|
|
840
|
-
// Find the pop-up's
|
|
858
|
+
// Find the pop-up's position
|
|
841
859
|
|
|
842
860
|
let location = null;
|
|
843
861
|
if ('positionAtLastClick' in options
|
|
@@ -850,7 +868,7 @@ export class UserInteractions
|
|
|
850
868
|
location = options.annotationFeatureGeometry;
|
|
851
869
|
} else {
|
|
852
870
|
// Position popup at the feature's 'centre'
|
|
853
|
-
location = this.__markerPosition(featureId,
|
|
871
|
+
location = this.__markerPosition(featureId, properties);
|
|
854
872
|
}
|
|
855
873
|
|
|
856
874
|
// Make sure the feature is on screen
|
|
@@ -866,10 +884,11 @@ export class UserInteractions
|
|
|
866
884
|
}
|
|
867
885
|
this._currentPopup.setLngLat(location);
|
|
868
886
|
if (typeof content === 'object') {
|
|
869
|
-
this._currentPopup.setDOMContent(content)
|
|
887
|
+
this._currentPopup.setDOMContent(content)
|
|
870
888
|
} else {
|
|
871
|
-
this._currentPopup.setText(content)
|
|
889
|
+
this._currentPopup.setText(content)
|
|
872
890
|
}
|
|
891
|
+
this._currentPopup.addTo(this._map)
|
|
873
892
|
}
|
|
874
893
|
}
|
|
875
894
|
|
|
@@ -1147,7 +1166,7 @@ export class UserInteractions
|
|
|
1147
1166
|
selectionEvent_(event, feature)
|
|
1148
1167
|
//=============================
|
|
1149
1168
|
{
|
|
1150
|
-
if (feature
|
|
1169
|
+
if (feature) {
|
|
1151
1170
|
const clickedFeatureId = +feature.id;
|
|
1152
1171
|
const dim = !('properties' in feature
|
|
1153
1172
|
&& 'kind' in feature.properties
|
|
@@ -1305,26 +1324,33 @@ export class UserInteractions
|
|
|
1305
1324
|
|
|
1306
1325
|
// Marker handling
|
|
1307
1326
|
|
|
1308
|
-
__markerPosition(featureId,
|
|
1327
|
+
__markerPosition(featureId, properties)
|
|
1309
1328
|
{
|
|
1310
1329
|
if (this.__markerPositions.has(featureId)) {
|
|
1311
1330
|
return this.__markerPositions.get(featureId);
|
|
1312
1331
|
}
|
|
1313
|
-
let position =
|
|
1314
|
-
if (position
|
|
1332
|
+
let position = properties.markerPosition || properties.centroid || null;
|
|
1333
|
+
if (!position) {
|
|
1315
1334
|
// Find where to place a label or popup on a feature
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
+
}
|
|
1328
1354
|
}
|
|
1329
1355
|
}
|
|
1330
1356
|
this.__markerPositions.set(featureId, position);
|
|
@@ -1338,7 +1364,7 @@ export class UserInteractions
|
|
|
1338
1364
|
let markerId = -1;
|
|
1339
1365
|
|
|
1340
1366
|
for (const featureId of featureIds) {
|
|
1341
|
-
const annotation = this
|
|
1367
|
+
const annotation = this.#getAnnotationProperties(featureId);
|
|
1342
1368
|
if (!('markerPosition' in annotation) && !annotation.geometry.includes('Polygon')) {
|
|
1343
1369
|
continue;
|
|
1344
1370
|
}
|
|
@@ -1454,7 +1480,7 @@ export class UserInteractions
|
|
|
1454
1480
|
const annotation = this.__annotationByMarkerId.get(markerId);
|
|
1455
1481
|
// The marker's feature
|
|
1456
1482
|
const feature = this.mapFeature(annotation.featureId);
|
|
1457
|
-
if (feature
|
|
1483
|
+
if (feature) {
|
|
1458
1484
|
if (event.type === 'mouseenter') {
|
|
1459
1485
|
// Highlight on mouse enter
|
|
1460
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
|