@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 CHANGED
@@ -26,7 +26,7 @@ Running
26
26
 
27
27
  ::
28
28
 
29
- $ npm start
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.8``
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.5.8",
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
- "start": "node app",
17
- "build": "webpack --mode production",
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
- "express": "^4.17.1",
51
- "html-webpack-plugin": "^4.5.2",
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.featureModeChanged.bind(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
- featureModeChanged(event)
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
- refreshGeometry(feature)
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
 
@@ -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.refreshGeometry(feature)
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 ann = this._flatmap.annotation(featureId);
464
- if (ann !== undefined) {
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
- ? `${ann['layer']}_${ann['tile-layer']}`
470
- : ann['tile-layer']).replaceAll('/', '_'),
471
- children: ann.children || []
494
+ ? `${properties['layer']}_${properties['tile-layer']}`
495
+ : properties['tile-layer']).replaceAll('/', '_'),
496
+ children: properties.children || []
472
497
  };
473
498
  }
474
- return undefined;
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 !== undefined) {
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 !== undefined) {
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 !== undefined) {
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 !== undefined
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 ann = this._flatmap.annotation(featureId);
578
- if (ann && 'sckan' in ann) {
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' && !ann.sckan
582
- || sckanState === 'invalid' && ann.sckan) {
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 !== undefined) {
623
+ if (feature) {
595
624
  const state = this.#getFeatureState(feature);
596
- if (state !== undefined && (!('hidden' in state) || !state.hidden)) {
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 !== undefined) {
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 !== undefined) {
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 !== undefined) {
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._flatmap.annotation(featureId);
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._flatmap.annotation(featureId);
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._flatmap.annotation(pathFeatureId)
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 ann = this._flatmap.annotation(featureId);
836
+ const properties = this.#getAnnotationProperties(featureId);
808
837
  const drawn = options && options.annotationFeatureGeometry;
809
- if (ann || drawn) { // The feature exists or it is a drawn annotation
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 postion
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, ann);
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 !== undefined) {
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 clickedFeature = clickedFeatures.filter((f)=>f.id)[0];
1189
- const clickedDrawnFeature = clickedFeatures.filter((f)=>!f.id)[0];
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 (clickedFeature !== undefined) {
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, annotation)
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 = annotation.markerPosition || annotation.centroid;
1303
- if (position === null || position == undefined) {
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
- const features = this._map.querySourceFeatures(VECTOR_TILES_SOURCE, {
1306
- 'sourceLayer': this._flatmap.options.separateLayers
1307
- ? `${annotation['layer']}_${annotation['tile-layer']}`
1308
- : annotation['tile-layer'],
1309
- 'filter': [
1310
- 'all',
1311
- [ '==', ['id'], parseInt(featureId) ],
1312
- [ '==', ['geometry-type'], 'Polygon' ]
1313
- ]
1314
- });
1315
- if (features.length > 0) {
1316
- position = labelPosition(features[0]);
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._flatmap.annotation(featureId);
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 !== undefined) {
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
+ //==============================================================================
@@ -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