-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
485 lines (452 loc) · 19.4 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
import EsriConfig from "https://js.arcgis.com/4.30/@arcgis/core/config.js";
import FeatureLayer from "https://js.arcgis.com/4.30/@arcgis/core/layers/FeatureLayer.js";
import GraphicsLayer from "https://js.arcgis.com/4.30/@arcgis/core/layers/GraphicsLayer.js";
import VectorTileLayer from "https://js.arcgis.com/4.30/@arcgis/core/layers/VectorTileLayer.js";
import Map from "https://js.arcgis.com/4.30/@arcgis/core/Map.js";
import MapView from "https://js.arcgis.com/4.30/@arcgis/core/views/MapView.js";
import Basemap from "https://js.arcgis.com/4.30/@arcgis/core/Basemap.js";
import BasemapToggle from "https://js.arcgis.com/4.30/@arcgis/core/widgets/BasemapToggle.js";
// import core Vue createApp and the markRaw utility we need to use with esri geometry objects (because they are so big and funky)
// dev
//import { createApp, markRaw } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
// prod
import { createApp, markRaw } from "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js";
import config from "./config.js";
// ToDo: consider using this TreeType component to display the tree types in the sidebar
// and manage the selection of tree types
import TreeType from './components/TreeType.js';
// Note that the widgets and other arc things will not work if we make them reactive data
// setting a global object, so sue me
// We don't want them to be reactive anyway
import { rogueState } from './rogueState.js';
createApp({
template: `
<div id="csu-trees-header">
<div class="color-blocks">
<div class="block csu-flower-trial-red-bg"></div>
<div class="block csu-energy-green-bg"></div>
<div class="block csu-white-bg"></div>
<div class="block csu-aggie-orange-bg"></div>
<div class="block csu-powdered-purple-bg"></div>
</div>
<div class="header-content">
<span id="more-info" class="is-size-6 csu-trees-action" @click="showingInformationModal=true">MORE INFO</span>
<div class="logo ml-4">
<figure class="image">
<img src="./assets/csu-logo.svg" alt="Colorado State University Logo" />
</figure>
</div>
<div class="title">
<h1 class="title is-3 has-text-white">CSU TREES</h1>
<h2 class="subtitle is-4 has-text-white">Colorado State University</h2>
</div>
<span id="clear-selection" v-show="selectedTreeTypes.length>0" class="is-size-6 csu-trees-action" @click="clearSelectedTrees">CLEAR SELECTED</span>
</div>
</div>
<div
id="notification"
v-show="isNotifying"
class="notification"
:class="{
'is-success': notification.type === 'success',
'is-warning': notification.type === 'warning',
'is-danger': notification.type === 'danger',
'is-info': notification.type === 'info'
}"
>
<button class="delete" @click="isNotifying=false"></button>
<p class="is-size-6">{{ notification.message }}</p>
</div>
<div class="content-container">
<main id="view" :class="[sidePanelIsCollapsed ? 'expanded' : '']"></main>
<aside id="right-sidebar" :class="[sidePanelIsCollapsed ? 'collapsed' : '']">
<div id="right-sidebar-content">
<div id="sidebar-header" class="has-text-centered csu-energy-green-bg csu-white">
<div class="title">ALL THE TREES</div>
<div class="subtitle"><span v-show="currentTreeTypes.length>0">{{ currentTreeTypes.length }}</span> Tree Types in the Current Map</div>
</div>
<div class="tree-types-container is-flex is-flex-wrap-wrap is-align-items-center m-4">
<div v-show="this.currentTreeTypes.length == 0" class="m-4 has-text-centered">
<div class="subtitle csu-green">Zoom in to see the types of trees displayed in the map</div>
<img id="centroid-logo" src="./assets/centroid-logo.png" alt="Centroid Logo" class="m-2 mt-6 ml-2">
</div>
<div
v-for="treeType in currentTreeTypes"
:key="treeType"
class="box tree-type has-text-centered is-clickable m-2"
:class="{ selected: selectedTreeTypes.includes(treeType) }"
@click="selectTreesByType(treeType)"
>
<span class="is-size-6">{{ treeType }}</span>
</div>
</div>
</div>
</aside>
</div>
<div id="information-modal" class="modal" :class="{ 'is-active': showingInformationModal }">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">CSU TREES INFORMATION</p>
<button class="delete" aria-label="close" @click="showingInformationModal=false"></button>
</header>
<div class="card">
<div class="card-content">
<p class="content">
<img width=200 src="./assets/centroid-logo.png" alt="CSU Centroid Logo showing a dark green hexagon with a large capital G in the center also shaped like a hexagon with a silhouette of a world map in the far background.">
<div class="csu-font-color">This project was developed through a partnership between CSU Facilities Management and the Geospatial Centroid at CSU.
The points shown here represent all of the trees on the campus of Colorado State University and is updated periodically.</div>
</p>
<p class="content">
The Geospatial Centroid, located in Morgan Library, is a resource and service center that supports the campus community on all aspects of geospatial technologies, including GIS (geographic information systems), remote sensing, web mapping and cartographic design. The Centroid employs student interns who gain hands-on skills while working on a wide and diverse range of projects for both on- and off-campus clients. For more information, drop by or visit: <a href="//www.gis.colostate.edu" target="_blank">https://gis.colostate.edu</a>
</p>
<small>
This application was developed by <a href="https://brightrain.com" target="_blank">Bright Rain Solutions</a>.
</small>
</div>
</div>
<footer class="modal-card-foot">
<button @click="showingInformationModal=false" class="button modal-close is-large" aria-label="close">OK</button>
</footer>
</div>
</div>
<img id="foothills-shortcut"
src="./assets/foothills.svg"
@click="goToFoothills"
width=65
alt="Foothills Campus Shortcut"
class="geoshortcut"
title="Go To Foothills Campus">
<img id="main-shortcut"
src="./assets/oval.svg"
@click="goToMain"
width=65
alt="Main Campus Shortcut"
class="geoshortcut"
title="Go To Main Campus">
`,
data() {
return {
selectedTreeTypes: [],
currentTreeTypes: [],
layersThreshold: 8000,
isThinking: false,
isNotifying: false,
notification: {
message: "",
type: "success" // success | warning | danger | info
},
sidePanelIsCollapsed: false,
showingInformationModal: false,
informationContent: config.informationContent,
debounceTimeout: null, // For debouncing the query
};
},
mounted() {
EsriConfig.apiKey = config.esriAPIKey;
const csuEngergyBasemap = new VectorTileLayer({ portalItem: { id: config.csuTreesBasemapPortalItemId } });
// custom vector tile basemap
let csuTreesBasemap = new Basemap({
baseLayers: [
csuEngergyBasemap
],
title: "CSU Base",
id: "streets",
thumbnailUrl: "./assets/csu-energy-basemap.png"
});
const map = new Map({
basemap: csuTreesBasemap
});
const view = new MapView({
map: map,
center: [-105.1, 40.58],
zoom: 13,
container: "view"
});
view.popup.dockEnabled = true;
view.popup.dockOptions = {
position: "bottom-right", // Positions: "top-left", "top-right", "bottom-left", "bottom-right"
breakpoint: false // Disable responsive dock options for small views
};
// this is the graphics layer used to hightlight selected trees
rogueState.selectedTreesGraphicsLayer = new GraphicsLayer();
map.add(rogueState.selectedTreesGraphicsLayer);
// Define the heatmap layer with shades of green
const heatmapLayer = new FeatureLayer({
portalItem: { id: config.csuTreesFeatureLayerPortalItemId },
renderer: {
type: "heatmap",
colorStops: [
{ color: "rgba(0, 128, 0, 0)", ratio: 0 }, // Transparent green (for low density)
{ color: "rgba(144, 238, 144, 0.5)", ratio: 0.2 }, // Light green
{ color: "rgba(60, 179, 113, 0.7)", ratio: 0.4 }, // Medium green
{ color: "rgba(34, 139, 34, 0.8)", ratio: 0.6 }, // Forest green
{ color: "rgba(0, 100, 0, 0.8)", ratio: 1 } // Dark green (for high density)
],
maxPixelIntensity: 100,
minDensity: 0
},
minScale: 0, // Visible at the closest zoom levels
maxScale: this.layersThreshold,
visible: true
});
// Add the heatmap layer to the map
map.add(heatmapLayer);
// Define the renderer for trees using unique values (deciduous and coniferous)
const treeRenderer = {
type: "unique-value", // Renderer type: unique-value based on a field
field: "DecidConif", // Field that classifies the trees (deciduous or coniferous)
uniqueValueInfos: [
{
// Deciduous tree symbol
value: "D", // Value for deciduous trees
symbol: {
type: "picture-marker", // Picture marker symbol for deciduous
url: "./assets/d-tree.png", // URL to the custom image
width: "24px",
height: "24px"
},
label: "Deciduous"
},
{
// Coniferous tree symbol
value: "C", // Value for coniferous trees
symbol: {
type: "picture-marker", // Picture marker symbol for coniferous
url: "./assets/c-tree.png", // URL to the custom image
width: "14px",
height: "19px"
},
label: "Coniferous"
}
]
};
rogueState.allTheTreesLayer = new FeatureLayer({
portalItem: { id: config.csuTreesFeatureLayerPortalItemId },
outFields: ["*"],
renderer: treeRenderer,
minScale: this.layersThreshold, // Visible at the closest zoom levels
maxScale: 0,
popupTemplate: {
title: "{New_Common}",
content: this.setTreeContent
}
});
map.add(rogueState.allTheTreesLayer);
let basemapToggle = new BasemapToggle({
view: view, // The view that provides access to the map's csu custom basemap
nextBasemap: "hybrid",
thumbnailUrl: "./assets/csu-energy-basemap.png"
});
view.ui.add(basemapToggle, {
position: "bottom-left"
});
view.ui.add("foothills-shortcut", "bottom-left");
view.ui.add("main-shortcut", "bottom-left");
view.whenLayerView(rogueState.allTheTreesLayer).then(layerView => {
this.handleLayerReady(layerView, view);
// Watch for extent changes and handle tree type queries
view.watch("stationary", (isStationary) => {
if (isStationary) {
if(rogueState.view.scale <= this.layersThreshold) {
this.queryTreeTypes(layerView, view);
}
else {
this.currentTreeTypes = [];
rogueState.selectedTreesGraphicsLayer.removeAll();
}
}
});
// Ensure the query runs when the layer finishes updating
layerView.watch("updating", (isUpdating) => {
if (!isUpdating) {
this.queryTreeTypes(layerView, view);
}
});
});
rogueState.view = view;
},
methods: {
handleLayerReady(layerView, view) {
this.queryTreeTypes(layerView, view);
},
queryTreeTypes(layerView, view) {
// Debounce the query to avoid multiple executions
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
}
this.debounceTimeout = setTimeout(() => {
let query = layerView.createQuery();
query.geometry = view.extent;
query.returnGeometry = false;
query.outFields = ["New_Common"];
layerView.queryFeatures(query).then((result) => {
let treeTypes = result.features.map(feature => feature.attributes.New_Common);
this.currentTreeTypes = [...new Set(treeTypes)].sort();
//console.log("Unique tree types within the current extent:", this.currentTreeTypes);
});
}, 300); // 300ms debounce delay
},
selectTreesByType(treeType) {
const index = this.selectedTreeTypes.indexOf(treeType);
if (index === -1) {
this.selectedTreeTypes.push(treeType);
} else {
this.selectedTreeTypes.splice(index, 1);
}
// Run the query against the trees feature layer with the selected tree types
// only if there are selected tree types
if (this.selectedTreeTypes.length > 0) {
this.queryTreesByTypes();
}
else {
rogueState.selectedTreesGraphicsLayer.removeAll();
}
this.queryTreesByTypes();
},
queryTreesByTypes() {
// run a query to get the tree features of the selected type using the current extent client-side
let query = rogueState.allTheTreesLayer.createQuery();
query.geometry = rogueState.view.extent;
query.where = `New_Common IN (${this.selectedTreeTypes.map(type => `'${type}'`).join(', ')})`;
query.returnGeometry = true;
query.outFields = ["New_Common"];
rogueState.allTheTreesLayer.queryFeatures(query).then((result) => {
// add the features to the graphics layer
rogueState.selectedTreesGraphicsLayer.removeAll();
result.features.forEach((feature) => {
let graphic = {
symbol: {
type: "simple-marker",
style: "circle",
color: "rgba(255, 255, 255, 0.1)",
size: "36px",
outline: {
color: "#D9782D", // Aggie Orange!
width: 2
}
},
geometry: feature.geometry
};
rogueState.selectedTreesGraphicsLayer.add(graphic);
});
});
},
setTreeContent(treeFeature) {
// set an image for the tree by type
/*
blue spruce
lodgepole pine
ponderosa pine
Austrian pine
pinyon pine
honeylocust
hackberry
green ash
crabapple
Ohio buckeye
*/
let properties = treeFeature.graphic.attributes;
let commonName = properties.New_Common.toString().toLowerCase();
let description = properties.DecidConif === "C" ? "Coniferous" : "Deciduous";
let content = `<table class="table is-bordered is-striped is-narrow is-fullwidth csu-font-color">
<tr><td>Common Name:</td><td class="font-bold">${properties.New_Common}</td></tr>
<tr><td>Family:</td><td>${properties.Family}</td></tr>
<tr><td>Species:</td><td>${properties.Genus_spec}</td></tr>
<tr><td>Cultivar:</td><td>${properties.Cultivar}</td></tr>
<tr><td>Type:</td><td>${description}</td></tr>
<tr><td>Campus:</td><td>${properties.Campus}</td></tr>
<tr><td colspan=2>${properties.Notes}</td></tr>
</table>`;
let imageElement = `<img src="./assets/c-tree.png">`;
switch (commonName) {
case "blue spruce":
imageElement = `<img src="https://csfs.colostate.edu/wp-content/uploads/2016/04/blue-spruce-tree.jpg" alt="blue spruce">`;
break;
case "boxelder":
imageElement = `<img src="https://csfs.colostate.edu/wp-content/uploads/2016/06/BoxelderTree.jpg" alt="boxelder">`;
break;
case "lodgepole pine":
imageElement = `<img src="https://csfs.colostate.edu/wp-content/uploads/2014/02/lodgepole-tree2.jpg" alt="lodgepole pine">`;
break;
case "ponderosa pine":
imageElement = `<img src="https://csfs.colostate.edu/wp-content/uploads/2014/02/ponderosa-tree-modern.jpg" alt="ponderosa pine">`;
break;
case "austrian pine":
imageElement = `<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/de/Pin_laricio_Corse.jpg/180px-Pin_laricio_Corse.jpg" alt="austrian pine">`;
break;
case "pinyon pine":
imageElement = `<img src="https://csfs.colostate.edu/wp-content/uploads/2016/04/pinon-tree.jpg" alt="pinyon pine">`;
break;
case "honeylocust":
case "honey locust":
imageElement = `<img src="http://tree-pictures.com/beautiful-honeylocust.jpg" alt="honeylocust">`;
break;
case "hackberry":
imageElement = `<img src="http://tree-pictures.com/hberrytree.jpg" alt="hackberry">`;
break;
case "green ash":
imageElement = `<img src="http://tree-pictures.com/ash-green.jpg" alt="green ash">`;
break;
case "crabapple":
imageElement = `<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Purple_prince_crabapple_tree.JPG/720px-Purple_prince_crabapple_tree.JPG" alt="crabapple">`;
break;
case "ohio buckeye":
imageElement = `<img src="https://upload.wikimedia.org/wikipedia/commons/5/53/Aesculus_glabra_var._glabra.jpg" alt="ohio buckeye">`;
break;
case "rocky mountain juniper":
imageElement = `<img src="https://csfs.colostate.edu/wp-content/uploads/2016/04/RMJuniper-tree.jpg" alt="rocky mountain juniper">`;
break;
case "littleleaf linden":
imageElement = `<img src="https://shop-static.arborday.org/media/0004010_littleleaf-linden_510.jpeg" alt="littleleaf linden">`;
break;
case "american basswood":
imageElement = `<img src="https://www.treehugger.com/thmb/Yk5DG-GIuVnSVf1xqYblh6qUJ70=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/GettyImages-469222318-e534a0dee0c94c428d128d68a71f9ae6.jpg" alt="american basswood">`;
break;
case "american elm":
imageElement = `<img src="https://upload.wikimedia.org/wikipedia/commons/8/86/American_Elm_Tree%2C_Old_South_Street%2C_Northampton%2C_MA_-_October_2019.jpg" alt="american elm">`;
break;
// default:
// if (properties.DecidConif === "D") {
// imageElement = `<img src="./assets/deciduous.svg" alt="deciduous tree">`;
// } else if (properties.DecidConif === "C") {
// imageElement = `<img src="./assets/coniferous.svg" alt="coniferous tree">`;
// } else {
// imageElement = `<img src="./assets/c-tree.png" alt="tree">`;
// }
// break;
}
return content + imageElement;
},
goToFoothills() {
rogueState.view.goTo({
target: [-105.15, 40.588],
zoom: 16
});
},
goToMain() {
rogueState.view.goTo({
target: [-105.084, 40.5758],
zoom: 16
});
},
clearSelectedTrees() {
this.selectedTreeTypes = [];
rogueState.selectedTreesGraphicsLayer.removeAll();
},
toggleSidePanel() {
this.sidePanelIsCollapsed = !this.sidePanelIsCollapsed;
},
notify(message, type, duration = 5000) {
this.notification.message = message;
this.notification.type = type;
this.isNotifying = true;
setTimeout(() => {
this.isNotifying = false;
}, duration);
}
},
components: {
'tree-type': TreeType
}
}).mount("#app");