1 import { controllers
} from './components/Controller.js';
2 import { simVars
} from './simVars.js';
3 import { map
} from './map.js';
5 /** Utility functions that can be imported and used in components from anywhere.
11 * - TimeConversion block
12 * - CreateDomElements block
14 * - Drag Elements block
18 /** ===== Constants block */
19 export const CLIENT_WIDTH
= document
.body
.clientWidth
;
20 export const IS_MOBILE
= CLIENT_WIDTH
< 769;
22 /** ===== SetURL block ===== */
23 export function setURL() {
27 const addData
= (key
, data
) => {
29 historyData
[key
] = data
;
30 urlVars
+= '&' + key
+ '=' + data
;
38 startDateToURL(addData
);
39 endDateToURL(addData
);
40 timestampToURL(addData
);
41 addedLayersToURL(addData
);
42 opacityToURL(addData
);
45 urlVars
= '?' + urlVars
.substr(1);
46 history
.pushState(historyData
, 'Data', urlVars
);
50 function zoomToURL(addData
) {
51 let zoom
= map
.getZoom();
52 addData('zoom', zoom
);
55 function panToURL(addData
) {
56 let center
= map
.getCenter();
57 let pan
= center
.lat
.toFixed(2) + ',' + center
.lng
.toFixed(2);
61 function jobIdToURL(addData
) {
62 let currentSimulation
= simVars
.currentSimulation
;
63 addData('job_id', currentSimulation
);
66 function domainToURL(addData
) {
67 let currentDomain
= controllers
.currentDomain
.getValue();
68 let domainInstances
= controllers
.domainInstance
.getValue();
69 if (domainInstances
!= null && domainInstances
.length
> 0 && currentDomain
!= domainInstances
[0]) {
70 addData('domain', currentDomain
);
74 function startDateToURL(addData
) {
75 let startDate
= controllers
.startDate
.getValue();
76 if (startDate
!= simVars
.sortedTimestamps
[0]) {
77 addData('startDate', utcToLocal(startDate
));
81 function endDateToURL(addData
) {
82 let endDate
= controllers
.endDate
.getValue();
83 let nTimestamps
= simVars
.sortedTimestamps
.length
;
84 if (endDate
!= simVars
.sortedTimestamps
[nTimestamps
- 1]) {
85 addData('endDate', utcToLocal(endDate
));
89 function timestampToURL(addData
) {
90 let timestamp
= controllers
.currentTimestamp
.getValue();
91 if (timestamp
!= startDate
) {
92 addData('timestamp', utcToLocal(timestamp
));
96 function addedLayersToURL(addData
) {
97 let rasterURL
= simVars
.overlayOrder
.join(',');
98 addData('rasters', rasterURL
);
101 function opacityToURL(addData
) {
102 let opacity
= controllers
.opacity
.getValue();
103 if (opacity
!= 0.5) {
104 addData('opacity', opacity
);
108 map
.on('zoomend', function() {
112 map
.on('moveend', function() {
116 /** ===== Debounce block ===== */
117 /** Executes function with a maximum rate of delay. */
118 export function debounceInIntervals(callback
, delay
) {
120 return function(args
=null) {
125 const callbackInIntervals
= () => {
128 timeout
= setTimeout(callbackInIntervals
, delay
);
132 /** Executes a function once at the end of an update cycle lasting delay. */
133 export function debounce(callback
, delay
) {
135 return function(args
=null) {
137 clearTimeout(timeout
);
139 timeout
= setTimeout(() => callback(args
), delay
);
143 /** ===== TimeConversion block ===== */
144 /** Function to convert UTC timestamp to PT timestamp. */
145 export function utcToLocal(utcTime
) {
149 let timezone
= 'America/Los_Angeles';
150 let localTime
= dayjs(utcTime
.replace('_', 'T') + 'Z');
152 return localTime
.format('YYYY-MM-DD HH:mm:ss', {timeZone
: timezone
});
155 export function localToUTC(localTime
) {
159 let timezone
= 'America/Los_Angeles';
160 let localTimeDayJS
= dayjs(localTime
).tz(timezone
);
161 let utcTime
= localTimeDayJS
.tz('UTC');
163 return utcTime
.format('YYYY-MM-DD_HH:mm:ss');
166 export function daysBetween(timestamp1
, timestamp2
) {
167 let date1
= dayjs(timestamp1
);
168 let date2
= dayjs(timestamp2
);
169 let diff
= date1
.diff(date2
, 'day');
170 return Math
.abs(diff
);
173 /** ===== CreateDomElements block ===== */
174 export function createOption(timeStamp
, utcValue
) {
175 let option
= document
.createElement('option');
176 option
.value
= timeStamp
;
177 let innerText
= utcValue
? utcToLocal(timeStamp
) : timeStamp
;
178 option
.innerText
= innerText
;
182 export function createElement(id
=null, className
=null) {
183 const div
= document
.createElement('div');
188 div
.className
= className
;
193 /** Creates the htmlElement for each checkbox in the LayerController. */
194 export function buildCheckBox(id
, type
, name
, checked
, callback
, args
=null) {
195 let div
= document
.createElement('div');
196 div
.className
= 'layer-checkbox';
198 const input
= document
.createElement('input');
202 input
.checked
= checked
;
203 input
.onclick
= () => {
207 let label
= document
.createElement('label');
209 label
.innerText
= id
;
210 div
.appendChild(input
);
211 div
.appendChild(label
);
215 export function linkSelects(selectStart
, selectEnd
) {
216 selectStart
.childNodes
.forEach(startOption
=> {
217 startOption
.disabled
= false;
218 if (startOption
.value
> selectEnd
.value
) {
219 startOption
.disabled
= true;
222 selectEnd
.childNodes
.forEach(endOption
=> {
223 endOption
.disabled
= false;
224 if (endOption
.value
< selectStart
.value
) {
225 endOption
.disabled
= true;
230 /** ===== Color block ===== */
231 // pulled from https://www.w3docs.com/snippets/javascript/how-to-convert-rgb-to-hex-and-vice-versa.html
232 export function rgbToHex(r
, g
, b
) {
233 return "#" + ((1 << 24) + (r
<< 16) + (g
<< 8) + b
).toString(16).slice(1);
236 export function darkenHex(hex
) {
237 let darkenedHex
= '#';
238 for (let decimal of hex
.substr(1)) {
265 darkenedHex
+= Number(decimal) - 2;
271 /** ===== DragElements block ===== */
272 /** A custom double click implementation needed to handle double clicking on ios. */
273 export function doubleClick(elmnt
, doubleClickFunction
) {
274 const DOUBLE_CLICK_MS
= 200;
275 const MAX_DOUBLE_CLICK_DIST
= 30;
278 elmnt
.addEventListener('pointerdown', (e
) => {
279 if (timeout
!= null) {
280 let xDiff
= Math
.abs(e
.clientX
- previousE
.clientX
);
281 let yDiff
= Math
.abs(e
.clientY
- previousE
.clientY
);
282 if ((xDiff
+ yDiff
) > MAX_DOUBLE_CLICK_DIST
) {
288 clearTimeout(timeout
);
291 doubleClickFunction(e
);
294 timeout
= setTimeout(() => {
301 /** Makes given element draggable from sub element with id 'subID' */
302 export function dragElement(elmnt
, subID
='', mobileEnabled
=false) {
303 let pos1
= 0, pos2
= 0, pos3
= 0, pos4
= 0;
304 let elmntLeft
= 0, elmntTop
= 0;
305 let clientWidth
= document
.body
.clientWidth
, clientHeight
= document
.body
.clientHeight
;
306 if (IS_MOBILE
&& !mobileEnabled
) {
309 let draggableElement
= document
.getElementById(elmnt
.id
);
311 draggableElement
= document
.getElementById(subID
);
313 // document.getElementById(elmnt.id + subID).onpointerdown = dragMouseDown;
314 // draggableElement.onpointerdown = dragMouseDown;
315 draggableElement
.addEventListener('pointerdown', dragMouseDown
);
316 window
.addEventListener('resize', () => {
317 let offsetLeft
= clientWidth
- document
.body
.clientWidth
;
318 if (elmntLeft
!= 0 && elmnt
.offsetLeft
+ (elmnt
.clientWidth
/ 2) > (document
.body
.clientWidth
/ 2) && (elmntLeft
- offsetLeft
) > 0) {
319 elmntLeft
= elmntLeft
- offsetLeft
;
320 elmnt
.style
.left
= elmntLeft
+ 'px';
322 let offsetTop
= clientHeight
- document
.body
.clientHeight
;
323 if (elmntTop
!= 0 && elmnt
.offsetTop
+ (elmnt
.clientHeight
/ 2) > (document
.body
.clientHeight
/ 2) && (elmntTop
- offsetTop
) > 0 && (elmntTop
- offsetTop
+ elmnt
.clientHeight
) < document
.body
.clientHeight
) {
324 elmntTop
= elmntTop
- offsetTop
;
325 elmnt
.style
.top
= elmntTop
+ 'px';
327 clientWidth
= document
.body
.clientWidth
;
328 clientHeight
= document
.body
.clientHeight
;
331 function dragMouseDown(e
) {
332 document
.body
.classList
.add('grabbing');
333 e
= e
|| window
.event
;
336 // get the mouse cursor position at startup:
339 document
.onpointerup
= closeDragElement
;
340 // call a function whenever the cursor moves:
341 document
.onpointermove
= elementDrag
;
344 function elementDrag(e
) {
345 e
= e
|| window
.event
;
348 // calculate the new cursor position:
349 pos1
= pos3
- e
.clientX
;
350 pos2
= pos4
- e
.clientY
;
353 if (elmntLeft
== 0) {
354 elmntLeft
= elmnt
.offsetLeft
;
355 elmntTop
= elmnt
.offsetTop
;
357 // set the element's new position:
358 if (Math
.abs(pos1
) >= 1 && elmntLeft
- pos1
> 0 && elmntLeft
+ elmnt
.clientWidth
- pos1
< clientWidth
) {
359 elmntLeft
= elmntLeft
- pos1
;
360 elmnt
.style
.left
= elmntLeft
+ 'px';
362 if (Math
.abs(pos2
) >= 1 && elmntTop
- pos2
> 0 && elmntTop
+ elmnt
.clientHeight
- pos2
< clientHeight
) {
363 elmntTop
= elmntTop
- pos2
;
364 elmnt
.style
.top
= elmntTop
+ 'px';
368 function closeDragElement() {
369 // stop moving when mouse button is released:
370 document
.body
.classList
.remove('grabbing');
371 document
.onpointerup
= null;
372 document
.onpointermove
= null;