imerss-bioinfo 1.0.0

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.
Files changed (182) hide show
  1. package/README.md +156 -0
  2. package/build.js +196 -0
  3. package/docs/css/imerss-viz-all.css +1227 -0
  4. package/docs/css/imerss-viz-wp-overrides.css +70 -0
  5. package/docs/css/pepiowelh.css +76 -0
  6. package/docs/css/xetthecum-external.css +47 -0
  7. package/docs/css/xetthecum-shared.css +80 -0
  8. package/docs/css/xetthecum.css +526 -0
  9. package/docs/js/imerss-viz-all.js +58 -0
  10. package/docs/js/imerss-viz-all.js.map +1 -0
  11. package/docs/js/jquery.js +10881 -0
  12. package/index.html +81 -0
  13. package/index.js +7 -0
  14. package/indexWithJustMap.html +92 -0
  15. package/indexWithMap.html +79 -0
  16. package/indexWithMapHoweSound.html +91 -0
  17. package/indexWithMapPepiowelh.html +119 -0
  18. package/indexWithMapValdes.html +90 -0
  19. package/indexWithMapXetthecum.html +90 -0
  20. package/package.json +50 -0
  21. package/src/WoRMS/taxonAPI.js +13 -0
  22. package/src/arphify.js +662 -0
  23. package/src/assignNames.js +103 -0
  24. package/src/auxBuild/restoreJQuery.js +1 -0
  25. package/src/buildSource/Comprehensive Lists.html +42 -0
  26. package/src/buildSource/Data Paper Part I Visualisation.html +50 -0
  27. package/src/buildSource/Galiano Life List.html +43 -0
  28. package/src/buildSource/Metchosin/index.html +22 -0
  29. package/src/buildSource/Pe'pi'ow'elh.html +72 -0
  30. package/src/buildSource/Squamish Life List.html +48 -0
  31. package/src/buildSource/Valdes Island Biodiversity.html +64 -0
  32. package/src/buildSource/Xetthecum.html +40 -0
  33. package/src/buildSource/dataPaperSunburstAndMap.html +30 -0
  34. package/src/buildSource/index.html +224 -0
  35. package/src/client/css/imerss-viz-wp-overrides.css +70 -0
  36. package/src/client/css/imerss-viz.css +531 -0
  37. package/src/client/css/pepiowelh.css +76 -0
  38. package/src/client/css/xetthecum-external.css +47 -0
  39. package/src/client/css/xetthecum-shared.css +80 -0
  40. package/src/client/css/xetthecum.css +526 -0
  41. package/src/client/html/imerss-viz-map-only.html +75 -0
  42. package/src/client/html/imerss-viz-map-pepi.html +77 -0
  43. package/src/client/html/imerss-viz-map-xetthecum.html +90 -0
  44. package/src/client/html/imerss-viz-map.html +75 -0
  45. package/src/client/html/imerss-viz.html +58 -0
  46. package/src/client/html/importing Xetthecum patterns.txt +10 -0
  47. package/src/client/html/xetthecum-patterns.html +27 -0
  48. package/src/client/img/Animalia_2.svg +1 -0
  49. package/src/client/img/Animalia_Annelida.svg +655 -0
  50. package/src/client/img/Animalia_Chaetognatha.svg +72 -0
  51. package/src/client/img/Animalia_Cnidaria.svg +173 -0
  52. package/src/client/img/Animalia_Ctenophora.svg +298 -0
  53. package/src/client/img/Animalia_Nemertea.svg +54 -0
  54. package/src/client/img/Animalia_Phoronida.svg +271 -0
  55. package/src/client/img/Animalia_Platyhelminthes.svg +58 -0
  56. package/src/client/img/Animalia_Porifera.svg +220 -0
  57. package/src/client/img/Animalia_Sipuncula.svg +155 -0
  58. package/src/client/img/Arachnoidiscus_schmidtii_web_1-2-scaled.webp +0 -0
  59. package/src/client/img/Arrow-down.svg +11 -0
  60. package/src/client/img/Arrow-up.svg +6 -0
  61. package/src/client/img/Chromista.svg +1 -0
  62. package/src/client/img/Pepiowelh/BlackNoWe2-wb-bb.png +0 -0
  63. package/src/client/img/Pepiowelh/BlackNoWe2-wb.png +0 -0
  64. package/src/client/img/Pepiowelh/BlackNoWe2.png +0 -0
  65. package/src/client/img/Pepiowelh/Ecocultural Mapping Banner.svg +1 -0
  66. package/src/client/img/Plankton.jpg +0 -0
  67. package/src/client/img/Plantae.svg +1 -0
  68. package/src/client/img/Plantae_2.svg +1 -0
  69. package/src/client/img/Protista.svg +1 -0
  70. package/src/client/img/Valdes/Valdes Island Conservancy.jpg +0 -0
  71. package/src/client/img/Xetthecum/Agricultural.jpg +0 -0
  72. package/src/client/img/Xetthecum/Anthropogenic_banner.jpg +0 -0
  73. package/src/client/img/Xetthecum/Broad_Leaf.jpg +0 -0
  74. package/src/client/img/Xetthecum/Caves.jpg +0 -0
  75. package/src/client/img/Xetthecum/Coastal_Bluffs.jpg +0 -0
  76. package/src/client/img/Xetthecum/Conifer_Pole_Sapling.jpg +0 -0
  77. package/src/client/img/Xetthecum/Developed.jpg +0 -0
  78. package/src/client/img/Xetthecum/Dock.jpg +0 -0
  79. package/src/client/img/Xetthecum/Eelgrass.jpg +0 -0
  80. package/src/client/img/Xetthecum/Forests_banner.jpg +0 -0
  81. package/src/client/img/Xetthecum/Freshwater_banner.jpg +0 -0
  82. package/src/client/img/Xetthecum/Greig_Creek.jpg +0 -0
  83. package/src/client/img/Xetthecum/Intertidal.jpg +0 -0
  84. package/src/client/img/Xetthecum/Laughlin_Lake.jpg +0 -0
  85. package/src/client/img/Xetthecum/Marine_banner.jpg +0 -0
  86. package/src/client/img/Xetthecum/Mature_Conifer_Forest.jpg +0 -0
  87. package/src/client/img/Xetthecum/Porlier_Pass_Road.jpg +0 -0
  88. package/src/client/img/Xetthecum/Riparian.jpg +0 -0
  89. package/src/client/img/Xetthecum/Rural.jpg +0 -0
  90. package/src/client/img/Xetthecum/Subtidal.jpg +0 -0
  91. package/src/client/img/Xetthecum/Wetland.jpg +0 -0
  92. package/src/client/img/Xetthecum/Woodland.jpg +0 -0
  93. package/src/client/img/Xetthecum/Woodland_banner.jpg +0 -0
  94. package/src/client/img/Xetthecum/Xetthecum.jpg +0 -0
  95. package/src/client/img/Xetthecum/Young_Conifer_Forest.jpg +0 -0
  96. package/src/client/img/Xetthecum/Young_Mixed_Forest.jpg +0 -0
  97. package/src/client/img/Xetthecum/food.svg +38 -0
  98. package/src/client/img/Xetthecum/indicator.svg +52 -0
  99. package/src/client/img/Xetthecum/material.svg +186 -0
  100. package/src/client/img/Xetthecum/medicinal.svg +62 -0
  101. package/src/client/img/Xetthecum/missing-value.svg +3 -0
  102. package/src/client/img/Xetthecum/spiritual.svg +13 -0
  103. package/src/client/img/Xetthecum/trade.svg +74 -0
  104. package/src/client/img/audio.svg +5 -0
  105. package/src/client/img/external-link.svg +3 -0
  106. package/src/client/img/selected-eye.svg +4 -0
  107. package/src/client/img/selected-tick.svg +3 -0
  108. package/src/client/img/thumbs/BCLeps.jpg +0 -0
  109. package/src/client/img/thumbs/Checklist.jpg +0 -0
  110. package/src/client/img/thumbs/Data-Paper-Figma.jpg +0 -0
  111. package/src/client/img/thumbs/Galiano-Data-Paper.jpg +0 -0
  112. package/src/client/img/thumbs/Galiano.jpg +0 -0
  113. package/src/client/img/thumbs/Maxwell.jpg +0 -0
  114. package/src/client/img/thumbs/Squamish.jpg +0 -0
  115. package/src/client/img/thumbs/Valdes.jpg +0 -0
  116. package/src/client/img/thumbs/Xetthecum.jpg +0 -0
  117. package/src/client/img/up-arrow.svg +4 -0
  118. package/src/client/js/autocomplete.js +53 -0
  119. package/src/client/js/checklist.js +179 -0
  120. package/src/client/js/colour.js +139 -0
  121. package/src/client/js/datasetControls.js +212 -0
  122. package/src/client/js/imerss-viz.js +1397 -0
  123. package/src/client/js/leafletMap.js +357 -0
  124. package/src/client/js/leafletMapWithGrid.js +337 -0
  125. package/src/client/js/leafletMapWithRegions.js +381 -0
  126. package/src/client/js/renderSVG.js +108 -0
  127. package/src/client/js/tabs.js +53 -0
  128. package/src/client/js/xetthecum.js +106 -0
  129. package/src/client/json/emptyPhyloMap.json +1 -0
  130. package/src/client/json/kingdomPhyloMap.json +17 -0
  131. package/src/client/json/phyloMap.json +58 -0
  132. package/src/compare.js +97 -0
  133. package/src/compilecsv.js +47 -0
  134. package/src/dataProcessing/LZ4.js +27 -0
  135. package/src/dataProcessing/coordinatePatch.js +151 -0
  136. package/src/dataProcessing/readCSV.js +107 -0
  137. package/src/dataProcessing/readCSVwithMap.js +90 -0
  138. package/src/dataProcessing/readCSVwithoutMap.js +25 -0
  139. package/src/dataProcessing/readJSON.js +23 -0
  140. package/src/dataProcessing/sqlite.js +174 -0
  141. package/src/dataProcessing/summarise.js +122 -0
  142. package/src/dataProcessing/taxonScrape.js +14 -0
  143. package/src/dataProcessing/worker.js +31 -0
  144. package/src/dataProcessing/writeCSV.js +34 -0
  145. package/src/dataProcessing/writeJSON.js +15 -0
  146. package/src/dbtaxa.js +96 -0
  147. package/src/deAuthorise.js +48 -0
  148. package/src/deqgis.js +148 -0
  149. package/src/expressions/expr.js +65 -0
  150. package/src/expressions/jsep.js +925 -0
  151. package/src/filterFirst.js +51 -0
  152. package/src/geom/geoJSON.js +121 -0
  153. package/src/iNaturalist/examples/api.inaturalist.org-v1-taxa!q=Acmispon americanus americanus.json +200 -0
  154. package/src/iNaturalist/examples/api.inaturalist.org-v1-taxa!q=Flabellina japonica.json +61 -0
  155. package/src/iNaturalist/examples/www.inaturalist.org-taxa-877359.json +132 -0
  156. package/src/iNaturalist/iNatUrls.js +17 -0
  157. package/src/iNaturalist/obsAPI.js +150 -0
  158. package/src/iNaturalist/taxonAPI.js +460 -0
  159. package/src/inatObs.js +120 -0
  160. package/src/inattify.js +123 -0
  161. package/src/intersect.js +80 -0
  162. package/src/intersectFilter.js +48 -0
  163. package/src/landlocked.js +137 -0
  164. package/src/lib/jquery.binarytransport.js +51 -0
  165. package/src/lib/lz4-commentary.txt +9 -0
  166. package/src/lib/lz4.js +73 -0
  167. package/src/lib/point-in-polygon.js +36 -0
  168. package/src/lz4.js +40 -0
  169. package/src/marmalise.js +333 -0
  170. package/src/materialise.js +68 -0
  171. package/src/reintegrate.js +71 -0
  172. package/src/taxonomise.js +615 -0
  173. package/src/unscrewGBIF.js +44 -0
  174. package/src/utils/dataSource.js +173 -0
  175. package/src/utils/settleStructure.js +62 -0
  176. package/src/utils/utils.js +92 -0
  177. package/src/viz/plethify.js +39 -0
  178. package/src/wormify.js +154 -0
  179. package/src/xetthecum/xetthecumFilters.js +126 -0
  180. package/test/testDataSource.js +182 -0
  181. package/test/testiNatObsSource.js +38 -0
  182. package/test/testiNatSource.js +50 -0
@@ -0,0 +1,357 @@
1
+ /* global L */
2
+
3
+ "use strict";
4
+
5
+ // noinspection ES6ConvertVarToLetConst // otherwise this is a duplicate on minifying
6
+ var hortis = fluid.registerNamespace("hortis");
7
+
8
+ // Workaround taken from https://github.com/Leaflet/Leaflet/issues/4745 to prevent coordinates being rounded to
9
+ // integers during rendering
10
+ L.Map.include({
11
+ latLngToLayerPoint: function (latlng) {
12
+ const projectedPoint = this.project(L.latLng(latlng));
13
+ const togo = projectedPoint._subtract(this.getPixelOrigin());
14
+ togo.x = fluid.roundToDecimal(togo.x, 3);
15
+ togo.y = fluid.roundToDecimal(togo.y, 3);
16
+ return togo;
17
+ }
18
+ });
19
+
20
+ fluid.defaults("hortis.leafletMap", {
21
+ gradeNames: ["fluid.viewComponent", "{sunburstLoader}.options.mapFlavourGrade"], // Not a distribution because of FLUID-5836
22
+ selectors: {
23
+ map: ".fld-imerss-map",
24
+ tooltip: ".fld-imerss-map-tooltip"
25
+ },
26
+ members: {
27
+ toPlot: {}, // General contract from grid map - indexed by "mapBlockId" to "bucket" - contains byTaxonId, count, Lpolygon (colours only used in drawGrid)
28
+ // byTaxonId is hash of rowId to array of obsId as built in hortis.quantiser.indexObs - so count is equal to # of keys
29
+ map: "@expand:L.map({that}.dom.map.0, {that}.options.mapOptions)"
30
+ },
31
+ datasets: {},
32
+ // Needs to be parameterised since leafletMapWithRegions and leafletMapWithGrid have different update semantics -
33
+ // regions is prone to an update cycle when clearing the selection and grid is not. Need to review this semantic
34
+ // carefully between the two variants
35
+ selectionTransactionSource: null,
36
+ model: {
37
+ mapInitialised: "@expand:{that}.events.buildMap.fire()",
38
+ // zoom: Number (0 - whole world -> 18 - maximal zoom)
39
+ datasetEnabled: "@expand:hortis.datasetEnabledModel({that}.options.datasets)",
40
+ // Terrible name which needs to be reviewed - common between map variants indicating "selected region"
41
+ mapBlockTooltipId: null
42
+ },
43
+ events: {
44
+ buildMap: null,
45
+ clearMapSelection: null
46
+ },
47
+ markup: {
48
+ tooltip: "<div class=\"fld-imerss-map-tooltip\"></div>",
49
+ grid: "<div class=\"fld-imerss-map-grid\"></div>",
50
+ tooltipHeader: "<table>",
51
+ tooltipRow: "<tr><td class=\"fl-taxonDisplay-key\">%key: </td><td class=\"fl-taxonDisplay-value\">%value</td>",
52
+ tooltipFooter: "</table>"
53
+ },
54
+ // fitBounds: [[48.855,-123.65],[49.005,-123.25]],
55
+ listeners: {
56
+ "buildMap.bindZoom": "hortis.leafletMap.bindZoom({that})",
57
+ "buildMap.fitBounds": "hortis.leafletMap.fitBounds({that}, {that}.options.fitBounds)",
58
+ "buildMap.createTooltip": "hortis.leafletMap.createTooltip({that}, {that}.options.markup)",
59
+ "buildMap.addTiles": "hortis.leafletMap.addTileLayer({that}.map, {that}.options.tileOptions)",
60
+ "clearMapSelection.mapBlock": {
61
+ changePath: "mapBlockTooltipId",
62
+ value: null
63
+ }
64
+ },
65
+ invokers: {
66
+ // Perhaps this will one day be a "materialiser registration" and we will instead call applier.pullModel("zoom")
67
+ acquireZoom: "hortis.leafletMap.acquireZoom({that})",
68
+ fitBounds: "hortis.leafletMap.fitBounds({that}, {arguments}.0, {arguments}.1)"
69
+ },
70
+ dynamicComponents: {
71
+ geoJSONLayers: { // Dumb, "global" geoJSON layers that are not interactive. Only historical example is json_Galiano_map_0, as per qgis2web exports
72
+ sources: "{that}.options.geoJSONMapLayers",
73
+ type: "hortis.geoJSONMapLayer",
74
+ options: {
75
+ layer: "{source}"
76
+ }
77
+ }
78
+ },
79
+ mapOptions: {
80
+ zoomSnap: 0.1
81
+ },
82
+ heatLow: "#ffffff"
83
+ // heatHigh: "#ff0000",
84
+ });
85
+
86
+ fluid.defaults("hortis.leafletMapWithWE", {
87
+ selectors: {
88
+ WEOverlay: ".fld-imerss-we-overlay"
89
+ },
90
+ listeners: {
91
+ "buildMap.bindWEClick": "hortis.leafletMap.bindWEClick({that})"
92
+ }
93
+ });
94
+
95
+ hortis.leafletMap.bindWEClick = function (map) {
96
+ const overlay = map.locate("WEOverlay");
97
+ overlay.click(function () {
98
+ map.map.flyToBounds(hortis.projectBounds.Pepiowelh, {duration: 2});
99
+ overlay.css("opacity", 0);
100
+ overlay.css("pointer-events", "none");
101
+ });
102
+ };
103
+
104
+ hortis.leafletMap.acquireZoom = function (map) {
105
+ map.applier.change("zoom", map.map.getZoom());
106
+ };
107
+
108
+ hortis.leafletMap.bindZoom = function (map) {
109
+ const leafletMap = map.map;
110
+ // Note that we can't apply the change at startup because of FLUID-5498
111
+ leafletMap.on("zoomend", function () {
112
+ map.acquireZoom();
113
+ });
114
+ leafletMap.on("moveend", function () {
115
+ console.log("Bounds now ", leafletMap.getBounds());
116
+ });
117
+ };
118
+
119
+ // Mixin grades for leafletMap selecting different tile layers
120
+ fluid.defaults("hortis.streetmapTiles", {
121
+ tileOptions: {
122
+ tileUrl: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
123
+ tileAttribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"
124
+ }
125
+ });
126
+
127
+ fluid.defaults("hortis.GBIFTiles", {
128
+ tileOptions: {
129
+ tileUrl: "https://tile.gbif.org/3857/omt/{z}/{x}/{y}@1x.png?style=gbif-classic",
130
+ tileAttribution: "&copy <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors, © <a href=\"https://openmaptiles.org/\">OpenMapTiles</a>, <a href=\"/citation-guidelines\">GBIF</a>"
131
+ }
132
+ });
133
+
134
+ fluid.defaults("hortis.MtbTiles", {
135
+ tileOptions: {
136
+ tileUrl: "http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png",
137
+ tileAttribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors &amp; USGS"
138
+ }
139
+ });
140
+
141
+ hortis.leafletMap.addTileLayer = function (map, tileOptions) {
142
+ if (tileOptions && tileOptions.tileUrl) {
143
+ L.tileLayer(tileOptions.tileUrl, {
144
+ attribution: tileOptions.tileAttribution
145
+ }).addTo(map);
146
+ }
147
+ };
148
+
149
+ hortis.datasetEnabledModel = function (datasets) {
150
+ return fluid.transform(datasets, function () {
151
+ return true;
152
+ });
153
+ };
154
+
155
+ fluid.defaults("hortis.geoJSONMapLayer", {
156
+ gradeNames: "fluid.component",
157
+ style: {
158
+ color: "black",
159
+ weight: 1.0
160
+ },
161
+ listeners: {
162
+ "onCreate.applyLayer": "hortis.applyGeoJSONMapLayer({that}, {hortis.leafletMap})"
163
+ }
164
+ });
165
+
166
+ hortis.applyGeoJSONMapLayer = function (mapLayer, map) {
167
+ L.geoJSON(mapLayer.options.layer, {
168
+ style: mapLayer.options.style
169
+ }).addTo(map.map);
170
+ };
171
+
172
+ hortis.leafletMap.fitBounds = function (map, fitBounds) {
173
+ if (fitBounds) {
174
+ map.map.fitBounds(fitBounds);
175
+ map.acquireZoom();
176
+ }
177
+ };
178
+
179
+ hortis.leafletMap.createTooltip = function (that, markup) {
180
+ const tooltip = $(markup.tooltip).appendTo(that.locate("map"));
181
+ tooltip.hide();
182
+ that.map.createPane("hortis-tooltip", tooltip[0]);
183
+ const container = that.map.getContainer();
184
+ $(container).on("click", function (event) {
185
+ if (event.target === container) {
186
+ that.events.clearMapSelection.fire();
187
+ }
188
+ });
189
+ $(document).on("click", function (event) {
190
+ const closest = event.target.closest(".fld-imerss-nodismiss-map");
191
+ // Mysteriously SVG paths are not in the document
192
+ if (!closest && event.target.closest("body")) {
193
+ that.events.clearMapSelection.fire();
194
+ }
195
+ });
196
+ };
197
+
198
+
199
+ hortis.projectBounds = {
200
+ Galiano: [[48.855, -123.65], [49.005, -123.25]],
201
+ Valdes: [[49.000, -123.798], [49.144, -123.504]],
202
+ Xetthecum: [[48.93713, -123.5110], [48.9511, -123.4980]],
203
+ Pepiowelh: [[48.5650, -123.1575], [48.5980, -123.1266]],
204
+ HoweSound: [[49.160, -124.281], [50.170, -122.050]],
205
+ SalishSea: [[47.568, -124.200], [49.134, -122.059]]
206
+ };
207
+
208
+ fluid.defaults("hortis.sunburstLoaderWithMap", {
209
+ gradeNames: "hortis.sunburstLoader",
210
+ selectors: {
211
+ mapHolder: ".fld-imerss-map-holder"
212
+ },
213
+ events: {
214
+ sunburstLoaded: null
215
+ },
216
+ mapBounds: hortis.projectBounds.Galiano,
217
+ mapGrades: [],
218
+ mapFlavourGrade: "hortis.leafletMap.withGrid", // swap out for "withRegions" for Xetthecum style map
219
+ markupTemplate: "%resourceBase/html/imerss-viz-map.html",
220
+ distributeOptions: {
221
+ sunburstLoaded: {
222
+ target: "{that sunburst}.options.listeners.onCreate",
223
+ record: "{hortis.sunburstLoaderWithMap}.events.sunburstLoaded.fire"
224
+ },
225
+ flatTree: {
226
+ target: "{that quantiser}.options.members.flatTree",
227
+ record: "@expand:fluid.identity({sunburst}.flatTree)"
228
+ },
229
+ mapGrades: {
230
+ target: "{that map}.options.gradeNames",
231
+ source: "{that}.options.mapGrades"
232
+ }
233
+ },
234
+ components: {
235
+ map: {
236
+ type: "hortis.leafletMap",
237
+ container: "{sunburstLoaderWithMap}.dom.mapHolder",
238
+ createOnEvent: "sunburstLoaded",
239
+ options: {
240
+ gradeNames: "hortis.mapWithSunburst",
241
+ fitBounds: "{hortis.configHolder}.options.mapBounds",
242
+ showObsListInTooltip: "{hortis.configHolder}.options.showObsListInTooltip"
243
+ }
244
+ }
245
+ }
246
+ });
247
+
248
+ // Mixin grade for sunburstLoader, currently used in Xetthecum driver
249
+ fluid.defaults("hortis.svgPatternLoader", {
250
+ svgPatterns: "%resourceBase/html/xetthecum-patterns.html",
251
+ resources: {
252
+ svgPatterns: {
253
+ url: "{that}.options.svgPatterns",
254
+ dataType: "text"
255
+ }
256
+ },
257
+ listeners: {
258
+ "onResourcesLoaded.injectPatterns": {
259
+ funcName: "hortis.injectSvgPatterns",
260
+ args: ["{svgPatternLoader}.resources.svgPatterns.resourceText", "{that}.options.resourceBase", "{that}.container"]
261
+ }
262
+ }
263
+ });
264
+
265
+ hortis.injectSvgPatterns = function (patternText, resourceBase, container) {
266
+ const expanded = patternText.replace(/%resourceBase/g, resourceBase);
267
+ const patterns = $(expanded);
268
+ container.append(patterns);
269
+ };
270
+
271
+ fluid.defaults("hortis.mapLoaderWithoutSunburst", {
272
+ // TODO: Refactor this obvious insanity
273
+ gradeNames: "hortis.sunburstLoaderWithMap",
274
+ markupTemplate: "%resourceBase/html/imerss-viz-map-only.html",
275
+ components: {
276
+ sunburst: {
277
+ options: {
278
+ model: {
279
+ visible: false
280
+ },
281
+ components: {
282
+ autocomplete: {
283
+ type: "fluid.emptySubcomponent"
284
+ },
285
+ tabs: {
286
+ type: "fluid.emptySubcomponent"
287
+ },
288
+ checklist: {
289
+ type: "fluid.emptySubcomponent"
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ });
296
+
297
+ fluid.defaults("hortis.mapWithSunburst", {
298
+ modelListeners: {
299
+ mapFocusedTooltipToSunburst: {
300
+ path: "{map}.model.mapBlockTooltipId",
301
+ func: "hortis.mapBlockToFocusedTaxa",
302
+ args: ["{change}.value", "{map}", "{sunburst}"]
303
+ }
304
+ },
305
+ datasets: "{sunburst}.viz.datasets",
306
+ geoJSONMapLayers: "{sunburstLoaderWithMap}.options.geoJSONMapLayers"
307
+ });
308
+
309
+ // Can't use modelRelay because of https://issues.fluidproject.org/browse/FLUID-6208
310
+ hortis.mapBlockToFocusedTaxa = function (mapBlockTooltipId, map, sunburst) {
311
+ const togo = {};
312
+ if (mapBlockTooltipId) {
313
+ const bucket = map.toPlot[mapBlockTooltipId];
314
+ if (bucket) {
315
+ fluid.each(bucket.byTaxonId, function (obs, taxonId) {
316
+ togo[taxonId] = true;
317
+ });
318
+ }
319
+ }
320
+ const source = map.options.selectionTransactionSource;
321
+ // As transaction to avoid triggering hortis.updateRowFocus twice which then invokes beginZoom
322
+ const trans = sunburst.applier.initiate(source);
323
+ trans.change("rowFocus", null, "DELETE", source);
324
+ trans.change("rowFocus", togo, null, source);
325
+ trans.commit();
326
+ };
327
+
328
+ // From https://en.wikipedia.org/wiki/Longitude#Length_of_a_degree_of_longitude
329
+ hortis.WGS84a = 6378137;
330
+ hortis.WGS84b = 6356752.3142;
331
+ hortis.WGS84e2 = (hortis.WGS84a * hortis.WGS84a - hortis.WGS84b * hortis.WGS84b) / (hortis.WGS84a * hortis.WGS84a);
332
+
333
+ /** Length in metres for a degree of longitude at given latitude **/
334
+
335
+ hortis.longitudeLength = function (latitude) {
336
+ const latrad = Math.PI * latitude / 180;
337
+ const sinrad = Math.sin(latrad);
338
+ return Math.PI * hortis.WGS84a * Math.cos(latrad) / (180 * Math.sqrt(1 - hortis.WGS84e2 * sinrad * sinrad));
339
+ };
340
+
341
+ /** Length in metres for a degree of latitude at given latitude **/
342
+
343
+ hortis.latitudeLength = function (latitude) {
344
+ const latrad = Math.PI * latitude / 180;
345
+ const sinrad = Math.sin(latrad);
346
+ return Math.PI * hortis.WGS84a * (1 - hortis.WGS84e2) / (180 * Math.pow(1 - hortis.WGS84e2 * sinrad * sinrad, 1.5));
347
+ };
348
+
349
+ hortis.longToLat = function (lng, lat) {
350
+ const longLength = hortis.longitudeLength(lat);
351
+ const latLength = hortis.latitudeLength(lat);
352
+ return lng * longLength / latLength;
353
+ };
354
+
355
+ hortis.quantiserDataset = function () {
356
+ return {maxCount: 0, totalCount: 0, buckets: {}, byTaxonId: {}};
357
+ };
@@ -0,0 +1,337 @@
1
+ /* global L */
2
+
3
+ "use strict";
4
+
5
+ // noinspection ES6ConvertVarToLetConst // otherwise this is a duplicate on minifying
6
+ var hortis = fluid.registerNamespace("hortis");
7
+
8
+ fluid.defaults("hortis.leafletMap.withGrid", {
9
+ selectors: {
10
+ grid: ".fld-imerss-map-grid",
11
+ datasetControls: ".fld-imerss-dataset-controls",
12
+ datasetsLabel: ".fld-imerss-datasets-label"
13
+ },
14
+ baseZoomForOutline: 11.9,
15
+ model: {
16
+ datasetsLabel: "Data Sources:"
17
+ },
18
+ modelListeners: {
19
+ drawGrid: {
20
+ path: ["indexVersion", "datasetEnabled"],
21
+ func: "{that}.drawGrid"
22
+ },
23
+ zoomToOutline: {
24
+ path: "zoom",
25
+ listener: "hortis.leafletMap.updateOutlineWidth",
26
+ args: ["{change}.value", "{that}.options.baseZoomForOutline", "{that}.container.0"]
27
+ },
28
+ drawScale: {
29
+ path: "{quantiser}.model.squareSide",
30
+ func: "hortis.leafletMap.drawScale",
31
+ args: ["{that}", "{change}.value"]
32
+ },
33
+ updateTooltip: {
34
+ path: ["mapBlockTooltipId", "indexVersion", "datasetEnabled"],
35
+ priority: "after:drawGrid",
36
+ excludeSource: "init",
37
+ func: "hortis.leafletMap.withGrid.updateTooltip",
38
+ args: ["{that}", "{that}.model.mapBlockTooltipId"]
39
+ },
40
+ updateTooltipHighlight: {
41
+ path: "mapBlockTooltipId",
42
+ excludeSource: "init",
43
+ func: "hortis.leafletMap.withGrid.updateTooltipHighlight",
44
+ args: ["{that}", "{change}.oldValue"]
45
+ }
46
+ },
47
+ invokers: {
48
+ drawGrid: "hortis.leafletMap.drawGrid({that}, {that}.quantiser, {that}.model.datasetEnabled)"
49
+ },
50
+ gridStyle: {
51
+ className: "fld-imerss-map-region"
52
+ },
53
+ listeners: {
54
+ "buildMap.addGrid": "hortis.leafletMap.withGrid.addGrid({that})"
55
+ },
56
+ modelRelay: {
57
+ renderDatasetControls: { // This could be refined someday if we are ever required to produce dataset controls with non-square map regions
58
+ target: "datasetControls",
59
+ func: "hortis.renderDatasetControls",
60
+ args: ["{that}.model.datasetEnabled", "{that}.quantiser.model.squareSide", "{that}.options.datasets", "{that}.quantiser", "{that}.model.indexVersion"]
61
+ },
62
+ datasetsLabel: {
63
+ source: "datasetsLabel",
64
+ target: "dom.datasetsLabel.text"
65
+ }
66
+ },
67
+ components: {
68
+ quantiser: {
69
+ type: "hortis.quantiser",
70
+ options: {
71
+ baseLatitude: "{leafletMap}.options.fitBounds.0.0",
72
+ datasets: "{leafletMap}.options.datasets",
73
+ model: {
74
+ indexVersion: "{leafletMap}.model.indexVersion"
75
+ }
76
+ }
77
+ }
78
+ },
79
+ dynamicComponents: {
80
+ datasetControls: {
81
+ sources: "{that}.model.datasetControls",
82
+ type: "{source}.type",
83
+ options: {
84
+ model: {
85
+ datasetControl: "{source}"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ });
91
+
92
+ hortis.leafletMap.updateOutlineWidth = function (zoom, baseZoom, container) {
93
+ const relativeZoom = Math.min(1, Math.pow(2, 2 * (zoom - baseZoom)));
94
+ container.style.setProperty("--imerss-stroke-width", 2 * relativeZoom);
95
+ };
96
+
97
+ hortis.leafletMap.withGrid.addGrid = function (that) {
98
+ that.map.createPane("hortis-grid");
99
+ that.gridGroup = L.layerGroup({pane: "hortis-grid"}).addTo(that.map);
100
+ };
101
+
102
+ hortis.leafletMap.drawScale = function (map, squareSide) {
103
+ const dimText = squareSide.toFixed(0) + "m";
104
+ map.container.find(".leaflet-bottom.leaflet-left").text("Block size: " + dimText + " x " + dimText);
105
+ };
106
+
107
+ hortis.rectFromCorner = function (tl, latres, longres) {
108
+ return [
109
+ [tl[0], tl[1]],
110
+ [tl[0], tl[1] + longres],
111
+ [tl[0] + latres, tl[1] + longres],
112
+ [tl[0] + latres, tl[1]]
113
+ ];
114
+ };
115
+
116
+ hortis.leafletMap.drawGrid = function (map, quantiser, datasetEnabled) {
117
+ map.gridGroup.clearLayers();
118
+
119
+ const latres = quantiser.model.latResolution, longres = quantiser.model.longResolution;
120
+ const heatLow = fluid.colour.hexToArray(map.options.heatLow);
121
+ const toPlot = map.toPlot = {};
122
+ fluid.each(quantiser.datasets, function (dataset, datasetId) {
123
+ if (datasetEnabled[datasetId]) {
124
+ const mapDataset = map.options.datasets[datasetId];
125
+ const heatHigh = fluid.colour.hexToArray(mapDataset.colour);
126
+ fluid.each(dataset.buckets, function (bucket, key) {
127
+ const prop = Math.pow(bucket.count / dataset.maxCount, 0.25);
128
+ const fillColour = fluid.colour.interpolate(prop, heatLow, heatHigh);
129
+ fluid.model.setSimple(toPlot, [key, "colours", datasetId], fillColour);
130
+ const plotBucket = toPlot[key];
131
+ plotBucket.count = plotBucket.count || 0;
132
+ plotBucket.count += bucket.count;
133
+ plotBucket.byTaxonId = fluid.extend({}, plotBucket.byTaxonId, bucket.byTaxonId);
134
+ });
135
+ }
136
+ });
137
+ fluid.each(toPlot, function (bucket, key) {
138
+ const colours = fluid.values(bucket.colours);
139
+ const colour = fluid.colour.average(colours);
140
+ const topLeft = hortis.quantiser.indexToCoord(key, latres, longres);
141
+ bucket.polygon = hortis.rectFromCorner(topLeft, latres, longres);
142
+ bucket.Lpolygon = L.polygon(bucket.polygon, fluid.extend({}, map.options.gridStyle, {
143
+ fillColor: fluid.colour.arrayToString(colour),
144
+ pane: "hortis-grid"
145
+ }));
146
+ map.gridGroup.addLayer(bucket.Lpolygon);
147
+ bucket.Lpolygon.on("click", function () {
148
+ console.log("Map clicked on key ", key);
149
+ map.applier.change("mapBlockTooltipId", key);
150
+ });
151
+ });
152
+ // Deal with overall loss of a block through not appearing in dataset selection
153
+ if (!toPlot[map.model.mapBlockTooltipId]) {
154
+ map.applier.change("mapBlockTooltipId", null);
155
+ }
156
+ };
157
+
158
+ hortis.leafletMap.tooltipRow = function (map, key, value) {
159
+ return fluid.stringTemplate(map.options.markup.tooltipRow, {key: key, value: value});
160
+ };
161
+
162
+ // TODO: Design fault, only responsible for REMOVING highlight, adding of highlight occurs in updateTooltip
163
+ hortis.leafletMap.withGrid.updateTooltipHighlight = function (map, oldKey) {
164
+ if (oldKey) {
165
+ const oldBucket = map.toPlot[oldKey];
166
+ if (oldBucket) {
167
+ const element = oldBucket.Lpolygon.getElement();
168
+ element.classList.remove("fl-imerss-highlightBlock");
169
+ }
170
+ }
171
+ };
172
+
173
+ // It is called "tooltip" but actually it is a popup panel activated on click
174
+ hortis.leafletMap.withGrid.updateTooltip = function (map, key) {
175
+ const tooltip = map.locate("tooltip");
176
+ const bucket = map.toPlot[key];
177
+ if (bucket) {
178
+ let text = map.options.markup.tooltipHeader;
179
+ const dumpRow = function (key, value) {
180
+ text += hortis.leafletMap.tooltipRow(map, key, value);
181
+ };
182
+ const resolution = map.quantiser.model.longResolution;
183
+ const dp = Math.max(3, 1 - Math.log10(resolution));
184
+ const c = function (value) {
185
+ return value.toFixed(dp);
186
+ };
187
+ dumpRow("Observation Count", bucket.count);
188
+ dumpRow("Species Richness", Object.values(bucket.byTaxonId).length);
189
+ const p = bucket.polygon;
190
+ const lat0 = p[0][0], lat1 = p[2][0];
191
+ const lng0 = p[0][1], lng1 = p[1][1];
192
+ dumpRow("Latitude", c(lat0) + " to " + c(lat1));
193
+ dumpRow("Longitude", c(lng0) + " to " + c(lng1));
194
+ if (bucket.count < 5 && map.options.showObsListInTooltip) {
195
+ const obs = fluid.flatten(Object.values(bucket.byTaxonId));
196
+ const obsString = fluid.transform(obs, hortis.renderObsId).map(s => "iNaturalist: " + s).join("<br/>");
197
+ dumpRow("Observations", obsString);
198
+ }
199
+ text += map.options.markup.tooltipFooter;
200
+ tooltip[0].innerHTML = text;
201
+ tooltip.show();
202
+ const element = bucket.Lpolygon.getElement();
203
+ element.classList.add("fl-imerss-highlightBlock");
204
+ const parent = element.parentNode;
205
+ parent.insertBefore(element, null);
206
+ } else {
207
+ tooltip.hide();
208
+ }
209
+ };
210
+
211
+
212
+ fluid.defaults("hortis.quantiser", {
213
+ gradeNames: "fluid.modelComponent",
214
+ baseLatitude: 51,
215
+ model: {
216
+ longResolution: 0.005,
217
+ indexVersion: 0
218
+ },
219
+ modelRelay: {
220
+ latResolution: {
221
+ target: "latResolution",
222
+ singleTransform: {
223
+ type: "fluid.transforms.free",
224
+ args: ["{that}.model.longResolution", "{that}.options.baseLatitude"],
225
+ func: "hortis.longToLat"
226
+ }
227
+ },
228
+ squareSide: {
229
+ target: "squareSide",
230
+ singleTransform: {
231
+ type: "fluid.transforms.free",
232
+ func: "hortis.quantiser.squareSide",
233
+ args: ["{that}.options.baseLatitude", "{that}.model.latResolution"]
234
+ }
235
+ },
236
+ index: {
237
+ target: "indexVersion",
238
+ singleTransform: {
239
+ type: "fluid.transforms.free",
240
+ args: ["{that}", "{that}.options.datasets", "{that}.model.latResolution", "{that}.model.longResolution", "{that}.model.squareSide", "{that}.model.indexVersion"],
241
+ func: "hortis.quantiser.indexTree"
242
+ }
243
+ }
244
+ },
245
+ members: {
246
+ datasets: {
247
+ // hash of datasetId to {maxCount, buckets}
248
+ // where buckets is hash of id to {count, byId}
249
+ }
250
+ },
251
+ events: {
252
+ indexUpdated: null
253
+ },
254
+ invokers: {
255
+ datasetToSummary: "hortis.quantiser.datasetToSummary({arguments}.0, {arguments}.1)", // bucket - will be modified
256
+ indexObs: "hortis.quantiser.indexObs({that}, {arguments}.0, {arguments}.1, {arguments}.2, {arguments}.3, {arguments}.4)" // coord, obsId, id, latRes, longRes
257
+ }
258
+ });
259
+
260
+ hortis.quantiser.squareSide = function (baseLatitude, latResolution) {
261
+ return hortis.latitudeLength(baseLatitude) * latResolution;
262
+ };
263
+
264
+ hortis.quantiser.indexToCoord = function (index, latres, longres) {
265
+ const coords = index.split("|");
266
+ return [coords[0] * latres, coords[1] * longres];
267
+ };
268
+
269
+ hortis.quantiser.coordToIndex = function (coord, latres, longres) {
270
+ if (coord) {
271
+ const lat = Math.floor(coord[0] / latres);
272
+ const lng = Math.floor(coord[1] / longres);
273
+ return lat + "|" + lng;
274
+ } else {
275
+ return "null";
276
+ }
277
+ };
278
+
279
+ hortis.datasetIdFromObs = function (obsId) {
280
+ const colpos = obsId.indexOf(":");
281
+ return obsId.substring(0, colpos);
282
+ };
283
+
284
+ hortis.localIdFromObs = function (obsId) {
285
+ const colpos = obsId.indexOf(":");
286
+ return obsId.substring(colpos + 1);
287
+ };
288
+
289
+ hortis.quantiser.indexObs = function (that, coord, obsId, rowId, latResolution, longResolution) {
290
+ const coordIndex = hortis.quantiser.coordToIndex(coord, latResolution, longResolution);
291
+ const datasetId = hortis.datasetIdFromObs(obsId);
292
+ const dataset = that.datasets[datasetId];
293
+ if (!dataset) {
294
+ fluid.fail("Found observation with unknown dataset " + datasetId);
295
+ }
296
+
297
+ dataset.byTaxonId[rowId] = true;
298
+ dataset.totalCount++;
299
+ if (coordIndex !== "null") {
300
+ let bucket = dataset.buckets[coordIndex];
301
+ if (!bucket) {
302
+ bucket = dataset.buckets[coordIndex] = {count: 0, byTaxonId: {}};
303
+ }
304
+ bucket.count++;
305
+ dataset.maxCount = Math.max(dataset.maxCount, bucket.count);
306
+ let bucketTaxa = bucket.byTaxonId[rowId]; // TODO: use fluid.pushArray?
307
+ if (!bucketTaxa) {
308
+ bucketTaxa = bucket.byTaxonId[rowId] = [];
309
+ }
310
+ bucketTaxa.push(obsId);
311
+ }
312
+ };
313
+
314
+ hortis.quantiser.datasetToSummary = function (dataset, squareSide) {
315
+ const squareArea = squareSide * squareSide / (1000 * 1000);
316
+ dataset.taxaCount = Object.keys(dataset.byTaxonId).length;
317
+ dataset.area = (Object.keys(dataset.buckets).length * squareArea).toFixed(2);
318
+ };
319
+
320
+ hortis.quantiser.indexTree = function (that, datasets, latResolution, longResolution, squareSide, indexVersion) {
321
+ that.datasets = fluid.transform(datasets, hortis.quantiserDataset);
322
+ for (let i = that.flatTree.length - 1; i >= 0; --i) {
323
+ const row = that.flatTree[i];
324
+ if (row.coords) {
325
+ const coords = JSON.parse(row.coords);
326
+ fluid.each(coords, function (coord, obsId) {
327
+ that.indexObs(coord, obsId, row.id, latResolution, longResolution);
328
+ });
329
+ }
330
+ };
331
+
332
+ fluid.each(that.datasets, function (dataset) {
333
+ that.datasetToSummary(dataset, squareSide);
334
+ });
335
+
336
+ return indexVersion + 1;
337
+ };