diff --git a/API/Backend/Utils/routes/utils.js b/API/Backend/Utils/routes/utils.js index 04051eab..065812c3 100644 --- a/API/Backend/Utils/routes/utils.js +++ b/API/Backend/Utils/routes/utils.js @@ -87,7 +87,8 @@ router.get("/queryTilesetTimes", function (req, res, next) { const split = name.split("Z-"); let t = split.shift(); const n = split.join(""); - t = t.replace(/_/g, ":") + "Z"; + t = t.replace(/_/g, ":"); + if (t[t.length - 1] !== "Z") t += "Z"; dirStore[relUrlSplit[0]].dirs.push({ t: t, n: n }); }); diff --git a/run/init-db.js b/run/init-db.js index c837103c..e380d8fc 100644 --- a/run/init-db.js +++ b/run/init-db.js @@ -39,6 +39,7 @@ async function initializeDatabase() { "connection" ); keepGoing(); + return null; }) .catch((err) => { logger( @@ -47,6 +48,7 @@ async function initializeDatabase() { "connection" ); keepGoing(); + return null; }); async function keepGoing() { @@ -76,6 +78,7 @@ async function initializeDatabase() { .then(() => { logger("info", `Created POSTGIS extension.`, "connection"); resolve(); + return null; }) .catch((err) => { logger( @@ -85,6 +88,7 @@ async function initializeDatabase() { ); resolve(); + return null; }); }) .catch((err) => { @@ -96,7 +100,9 @@ async function initializeDatabase() { err ); reject(); + return null; }); } + return null; }); } diff --git a/src/essence/Ancillary/Coordinates.js b/src/essence/Ancillary/Coordinates.js index 2fb4701a..3383c6ce 100644 --- a/src/essence/Ancillary/Coordinates.js +++ b/src/essence/Ancillary/Coordinates.js @@ -765,6 +765,8 @@ function toggleTimeUI() { const newBottom = active ? 0 : 40 const timeBottom = active ? -40 : 0 + Map_.map._fadeAnimated = active + $('#CoordinatesDiv').css({ bottom: newBottom + (UserInterface.pxIsTools || 0) + 'px', }) diff --git a/src/essence/Ancillary/TimeControl.js b/src/essence/Ancillary/TimeControl.js index 01f0a01f..bdb2f62e 100644 --- a/src/essence/Ancillary/TimeControl.js +++ b/src/essence/Ancillary/TimeControl.js @@ -37,12 +37,9 @@ var TimeControl = { return } - TimeControl.timeUI = TimeUI.init(timeInputChange) + TimeControl.timeUI = TimeUI.init(timeInputChange, TimeControl.enabled) //updateTime() - if (L_.configData.time.visible == false) { - TimeControl.toggleTimeUI(false) - } initLayerTimes() initLayerDataTimes() @@ -51,14 +48,6 @@ var TimeControl = { if ((TimeControl.enabled = true && TimeControl.timeUI != null)) TimeControl.timeUI.fina() }, - toggleTimeUI: function (isOn) { - d3.select('#timeUI').style('visibility', function () { - if (!isOn) $('#toggleTimeUI').click() - - return isOn === true ? 'visible' : 'hidden' - }) - return isOn - }, setTime: function ( startTime, endTime, @@ -173,8 +162,7 @@ var TimeControl = { } if (L_.toggledArray[layer.name] || evenIfOff) { - L_.toggleLayer(layer) - L_.toggleLayer(layer) + L_.layersGroup[layer.name].refresh() } } else { var originalUrl = layer.url diff --git a/src/essence/Ancillary/TimeUI.css b/src/essence/Ancillary/TimeUI.css index b13fa6ea..28d9e815 100644 --- a/src/essence/Ancillary/TimeUI.css +++ b/src/essence/Ancillary/TimeUI.css @@ -1,5 +1,5 @@ #timeUI { - background: rgba(29, 31, 32, 0.65); + background: rgba(15, 16, 16, 0.6); height: 40px; width: 100%; position: absolute; @@ -86,7 +86,7 @@ font-size: 11px; text-transform: uppercase; pointer-events: none; - color: var(--color-a5); + color: var(--color-h); } #mmgisTimeUITimeline { @@ -95,14 +95,6 @@ position: relative; user-select: none; } -#mmgisTimeUITimelineFixedStartSlider { - position: absolute; - left: 200px; - width: 4px; - height: 40px; - background: var(--color-h); - pointer-events: none; -} #mmgisTimeUITimelineSlider { width: 100%; } @@ -110,18 +102,13 @@ background: var(--color-h); height: 2px; opacity: 0.75; + pointer-events: none; } #mmgisTimeUITimelineSlider .rangeSlider { height: 2px; margin: 20px 0px 20px 4px; background: var(--color-a2); } -#mmgisTimeUITimelineSlider .rangeHandle:first-child { - display: none; - pointer-events: none; - width: 0px; - padding: 0px; -} #mmgisTimeUITimelineSlider .rangeHandle { top: 0px; width: 20px; @@ -144,6 +131,11 @@ margin: 0px 7px; } +#mmgisTimeUITimelineInner { + height: 100%; + width: 100%; + position: absolute; +} .mmgisTimeUITick { position: absolute; top: 0; @@ -156,14 +148,13 @@ } .mmgisTimeUITick .mmgisTimeUITickBig { height: 40px; - background: var(--color-a4); + background: var(--color-a3); width: 1px; } .mmgisTimeUITick .mmgisTimeUITickLabel { height: 40px; line-height: 20px; padding-left: 4px; - text-transform: uppercase; font-size: 13px; color: var(--color-a7); } @@ -215,23 +206,40 @@ pointer-events: none; } #mmgisTimeUITimelineHisto > div { - background: var(--color-c2); - opacity: 0.38; + background: #056e94; height: 100%; transition: margin-top 0.2s ease-in-out; } +#mmgisTimeUIModeDropdown { + background: var(--color-a-5); + width: 80px; +} +#mmgisTimeUIStepDropdown { + width: 80px; +} #mmgisTimeUIRateDropdown { width: 60px; } +#mmgisTimeUIModeDropdown .dropy__title i, +#mmgisTimeUIStepDropdown .dropy__title i, #mmgisTimeUIRateDropdown .dropy__title i { color: var(--color-a5); } +#mmgisTimeUIModeDropdown .dropy__title span, +#mmgisTimeUIStepDropdown .dropy__title span, #mmgisTimeUIRateDropdown .dropy__title span { font-size: 12px; padding: 14px 0px 14px 12px; color: var(--color-a5); } +#mmgisTimeUIModeDropdown .dropy__title span { + color: var(--color-a5); + text-transform: uppercase; +} + +#mmgisTimeUIModeDropdown li a, +#mmgisTimeUIStepDropdown li a, #mmgisTimeUIRateDropdown li a { font-size: 13px; padding: 5px 8px; diff --git a/src/essence/Ancillary/TimeUI.js b/src/essence/Ancillary/TimeUI.js index fcc9eff2..59d00947 100644 --- a/src/essence/Ancillary/TimeUI.js +++ b/src/essence/Ancillary/TimeUI.js @@ -8,6 +8,7 @@ import $ from 'jquery' import * as d3 from 'd3' import * as moment from 'moment' import F_ from '../Basics/Formulae_/Formulae_' +import Map_ from '../Basics/Map_/Map_' import L_ from '../Basics/Layers_/Layers_' import calls from '../../pre/calls' import tippy from 'tippy.js' @@ -21,8 +22,10 @@ import './TimeUI.css' const FORMAT = 'MM/DD/yyyy, hh:mm:ss A' const MS = { + decade: 315576000000, year: 31557600000, month: 2629800000, + week: 604800000, day: 86400000, hour: 3600000, minute: 60000, @@ -36,6 +39,8 @@ const TimeUI = { _startTimestamp: null, _endTimestamp: null, _timeSliderTimestamp: null, + _timelineStartTimestamp: null, + _timelineEndTimestamp: null, now: false, numFrames: 24, intervalIndex: 6, @@ -52,18 +57,31 @@ const TimeUI = { '10s', '20s', ], + stepIndex: 3, + modes: ['Range', 'Point'], + modeIndex: 0, _initialStart: null, _initialEnd: null, - init: function (timeChange) { + _tickTippies: [], + init: function (timeChange, enabled) { TimeUI.timeChange = timeChange + TimeUI.enabled = enabled // prettier-ignore const markup = [ `
`, `
`, + `
`, + ``, + `
`, + `
`, `
`, ``, `
`, `
`, + `
`, + ``, + `
`, + `
`, `
`, ``, `
`, @@ -73,14 +91,13 @@ const TimeUI = { `Start Time`, ``, `
`, - `
`, `
`, `
`, `
`, `
`, `
`, `
`, - `End Time`, + `Active Time`, ``, `
`, `
`, @@ -89,10 +106,12 @@ const TimeUI = { ``, ``, ``, + /* `
`, `
Active Time
`, `
`, `
`, + */ `` ].join('\n') @@ -107,6 +126,76 @@ const TimeUI = { }, getElement: function () {}, attachEvents: function (timeChange) { + // Timeline pan and zoom + // zoom + $('#mmgisTimeUITimelineInner').on('mousewheel', function (e) { + if (TimeUI.play) return + const x = e.originalEvent.offsetX + const width = document + .getElementById('mmgisTimeUITimelineInner') + .getBoundingClientRect().width + // As opposed to per-cent + const perun = x / width + const direction = e.originalEvent.deltaY > 0 ? 1 : -1 + const AMOUNT = 0.2 + const dif = + TimeUI._timelineEndTimestamp - TimeUI._timelineStartTimestamp + const maxChangeAmount = dif * AMOUNT + + const nextStart = + TimeUI._timelineStartTimestamp - + 0 - + perun * maxChangeAmount * direction + const nextEnd = + TimeUI._timelineEndTimestamp - + 0 + + (1 - perun) * maxChangeAmount * direction + + TimeUI._drawTimeLine(nextStart, nextEnd) + + clearTimeout(TimeUI._zoomHistoTimeout) + $('#mmgisTimeUITimelineHisto').empty() + TimeUI._zoomHistoTimeout = setTimeout(() => { + TimeUI._makeHistogram() + }, 3000) + }) + + // pan + $('#mmgisTimeUITimelineInner').on('mousedown', function () { + if (TimeUI.play) { + TimeUI._timelineDragging = false + return + } + TimeUI._timelineDragging = true + $('#mmgisTimeUITimelineSlider').css({ pointerEvents: 'none' }) + $('#mmgisTimeUITimelineInner').on('mousemove', TimeUI._timelineDrag) + }) + $('#mmgisTimeUITimelineInner').on('mouseout', function () { + if (TimeUI._timelineDragging === true) { + $('#mmgisTimeUITimelineSlider').css({ + pointerEvents: 'inherit', + }) + $('#mmgisTimeUITimelineInner').off( + 'mousemove', + TimeUI._timelineDrag + ) + TimeUI._timelineDragging = false + } + }) + $('#mmgisTimeUITimelineInner').on('mouseup', function () { + if (TimeUI._timelineDragging === true) { + $('#mmgisTimeUITimelineSlider').css({ + pointerEvents: 'inherit', + }) + $('#mmgisTimeUITimelineInner').off( + 'mousemove', + TimeUI._timelineDrag + ) + TimeUI._timelineDragging = false + } + }) + + // Time const options = { display: { viewMode: 'months', @@ -157,33 +246,43 @@ const TimeUI = { // Don't let end date be before start date TimeUI.startTempus.subscribe(Namespace.events.change, (e) => { - TimeUI.setStartTime( - moment.utc(e.date).toISOString(), - TimeUI.startTempus.dontChangeNext - ) - TimeUI.endTempus.updateOptions({ - restrictions: { - minDate: e.date, - }, - }) - TimeUI._remakeTimeSlider() - TimeUI.startTempus.dontChangeNext = false + if (TimeUI.startTempus.dontChangeAnythingElse !== true) { + TimeUI.setStartTime( + moment.utc(e.date).toISOString(), + TimeUI.startTempus.dontChangeNext + ) + TimeUI.startTempusSavedLastDate = e.date + TimeUI.endTempus.updateOptions({ + restrictions: { + minDate: e.date, + }, + }) + TimeUI._remakeTimeSlider() + TimeUI.startTempus.dontChangeNext = false + } + TimeUI.startTempus.dontChangeAnythingElse = false }) // Don't let start date be after end date TimeUI.endTempus.subscribe(Namespace.events.change, (e) => { - if (TimeUI._startTimestamp != null) { + if ( + TimeUI._startTimestamp != null && + TimeUI.endTempus.dontChangeAnythingElse !== true + ) { TimeUI.setEndTime( moment.utc(e.date).toISOString(), TimeUI.endTempus.dontChangeNext ) + TimeUI.endTempusSavedLastDate = e.date TimeUI.startTempus.updateOptions({ restrictions: { maxDate: e.date, }, }) + TimeUI._remakeTimeSlider() TimeUI.endTempus.dontChangeNext = false } + TimeUI.endTempus.dontChangeAnythingElse = false }) // Disable Now when picking so that date restrictions don't keep applying and hiding calendar @@ -201,13 +300,23 @@ const TimeUI = { }) // tippy + tippy('#mmgisTimeUIMode', { + content: 'Mode', + placement: 'top', + theme: 'blue', + }) tippy('#mmgisTimeUIPlay', { content: 'Play', placement: 'top', theme: 'blue', }) + tippy('#mmgisTimeUIStep', { + content: 'Step Size', + placement: 'top', + theme: 'blue', + }) tippy('#mmgisTimeUIRate', { - content: 'Frame Duration', + content: 'Step Duration', placement: 'top', theme: 'blue', }) @@ -217,12 +326,69 @@ const TimeUI = { theme: 'blue', }) - $('#mmgisTimeUIRateDropdown').html( - Dropy.construct(TimeUI.intervalNames, null, TimeUI.intervalIndex, { + // Mode dropdown + $('#mmgisTimeUIModeDropdown').html( + Dropy.construct(TimeUI.modes, 'Mode', TimeUI.modeIndex, { openUp: true, dark: true, }) ) + Dropy.init($('#mmgisTimeUIModeDropdown'), function (idx) { + TimeUI.modeIndex = idx + if (TimeUI.modes[TimeUI.modeIndex] === 'Point') { + $('#mmgisTimeUIStartWrapper').css({ display: 'none' }) + // Remove end date enforcement + TimeUI.endTempus.updateOptions({ + restrictions: { + minDate: new Date(0).toISOString(), + }, + }) + } else { + $('#mmgisTimeUIStartWrapper').css({ display: 'inherit' }) + // Reinforce min date + TimeUI.endTempus.updateOptions({ + restrictions: { + minDate: TimeUI.startTempusSavedLastDate, + }, + }) + if (TimeUI._startTimestamp >= TimeUI._endTimestamp) { + const offsetStartDate = new Date(TimeUI._endTimestamp) + const parsedStart = TimeUI.startTempus.dates.parseInput( + new Date(offsetStartDate) + ) + TimeUI.startTempus.dates.setValue(parsedStart) + } + } + TimeUI._remakeTimeSlider(true) + }) + // Step dropdown + $('#mmgisTimeUIStepDropdown').html( + Dropy.construct( + Object.keys(MS).map((k) => k.capitalizeFirstLetter()), + 'Step', + TimeUI.stepIndex, + { + openUp: true, + dark: true, + } + ) + ) + Dropy.init($('#mmgisTimeUIStepDropdown'), function (idx) { + TimeUI.stepIndex = idx + TimeUI._refreshIntervals() + }) + // Rate dropdown + $('#mmgisTimeUIRateDropdown').html( + Dropy.construct( + TimeUI.intervalNames, + 'Rate', + TimeUI.intervalIndex, + { + openUp: true, + dark: true, + } + ) + ) Dropy.init($('#mmgisTimeUIRateDropdown'), function (idx) { TimeUI.intervalIndex = idx TimeUI._refreshIntervals() @@ -329,18 +495,34 @@ const TimeUI = { TimeUI._remakeTimeSlider() TimeUI._setCurrentTime(true, savedEndDate) + + if (TimeUI.enabled) { + TimeUI._makeHistogram() + } }, - togglePlay() { - if (TimeUI.play) { + togglePlay(force) { + const mode = TimeUI.modes[TimeUI.modeIndex] + if (TimeUI.play || force === false) { $('#mmgisTimeUIPlay') .css('background', '') .css('color', 'var(--color-a4)') TimeUI.play = false + + // Don't reposition active time on Stop for Point Mode + if (mode === 'Point') TimeUI._savedPlayEnd = null + + // But do for Range Mode + if (TimeUI._savedPlayEnd != null) { + TimeUI.setCurrentTime(TimeUI._savedPlayEnd) + TimeUI._remakeTimeSlider(true) + TimeUI._savedPlayEnd = null + } } else { $('#mmgisTimeUIPlay') .css('background', 'var(--color-p4)') .css('color', 'white') TimeUI.play = true + TimeUI._savedPlayEnd = TimeUI.getCurrentTimestamp() TimeUI.now = false $('#mmgisTimeUIPresent') .css('background', '') @@ -353,6 +535,7 @@ const TimeUI = { _refreshIntervals() { clearInterval(TimeUI.playInterval) if (TimeUI.play) { + TimeUI._loopTime() TimeUI.playInterval = setInterval( TimeUI._loopTime, TimeUI.intervalValues[TimeUI.intervalIndex] @@ -368,15 +551,25 @@ const TimeUI = { } }, _loopTime() { - const start = TimeUI._startTimestamp - const end = TimeUI._endTimestamp + const mode = TimeUI.modes[TimeUI.modeIndex] + const start = + mode === 'Range' + ? TimeUI._startTimestamp + : TimeUI._timelineStartTimestamp + + const end = + mode === 'Range' + ? TimeUI._endTimestamp + : TimeUI._timelineEndTimestamp const current = TimeUI.getCurrentTimestamp() - let next = (end - start) / TimeUI.numFrames + current + let next = current + MS[Object.keys(MS)[TimeUI.stepIndex]] if (next > end) next = end if (current === end) next = start - TimeUI.setCurrentTime(next) + if (mode === 'Range') TimeUI.setCurrentTime(next) + if (mode === 'Point') TimeUI.setCurrentTime(next, null, null, true) + TimeUI._remakeTimeSlider(true) }, toggleTimeNow(force) { @@ -387,10 +580,7 @@ const TimeUI = { $('#mmgisTimeUIEnd').css('pointer-events', 'none') $('#mmgisTimeUIEndWrapper').css('cursor', 'not-allowed') TimeUI.now = true - TimeUI.play = false - $('#mmgisTimeUIPlay') - .css('background', '') - .css('color', 'var(--color-a4)') + TimeUI.togglePlay(false) } else { clearInterval(TimeUI.presentTimeInterval) $('#mmgisTimeUIPresent') @@ -403,19 +593,28 @@ const TimeUI = { TimeUI._refreshIntervals() }, _remakeTimeSlider(ignoreHistogram) { + const rangeMode = + TimeUI.modes[TimeUI.modeIndex] === 'Range' ? true : false if (TimeUI.timeSlider) { $('#mmgisTimeUITimelineSlider').empty() TimeUI.timeSlider = null + if (rangeMode) $('#mmgisTimeUITimelineSlider').addClass('rangeMode') + else $('#mmgisTimeUITimelineSlider').removeClass('rangeMode') } TimeUI.timeSlider = new RangeSliderPips({ target: document.querySelector('#mmgisTimeUITimelineSlider'), props: { - values: [TimeUI._startTimestamp, TimeUI.getCurrentTimestamp()], + values: rangeMode + ? [ + TimeUI.removeOffset(TimeUI._startTimestamp), + TimeUI.removeOffset(TimeUI.getCurrentTimestamp()), + ] + : [TimeUI.removeOffset(TimeUI.getCurrentTimestamp())], pips: false, - min: TimeUI._startTimestamp, - max: TimeUI._endTimestamp, - range: true, + min: TimeUI._timelineStartTimestamp, + max: TimeUI._timelineEndTimestamp, + range: rangeMode, pushy: false, float: false, springValues: { @@ -428,28 +627,66 @@ const TimeUI = { }, }) - $('#mmgisTimeUICurrentTime').text( - moment.utc(TimeUI.getCurrentTimestamp(true)).format(FORMAT) - ) - TimeUI.timeSlider.$on('start', (e) => { TimeUI.toggleTimeNow(false) + if (TimeUI.play) { + TimeUI._savedPlayEnd = null + TimeUI.togglePlay(false) + } }) TimeUI.timeSlider.$on('change', (e) => { - $('#mmgisTimeUICurrentTime').text( - moment.utc(TimeUI.removeOffset(e.detail.value)).format(FORMAT) + let idx = 0 + if (TimeUI.modes[TimeUI.modeIndex] === 'Point') idx -= 1 + + const date = new Date(e.detail.value) + const offsetNowDate = new Date( + date.getTime() + date.getTimezoneOffset() * 60000 ) + if (e.detail.activeHandle === idx) { + const parsedNow = TimeUI.startTempus.dates.parseInput( + new Date(offsetNowDate) + ) + TimeUI.startTempus.dontChangeAnythingElse = true + TimeUI.startTempus.dates.setValue(parsedNow) + } + if (e.detail.activeHandle === idx + 1) { + const parsedNow = TimeUI.endTempus.dates.parseInput( + new Date(offsetNowDate) + ) + TimeUI.endTempus.dontChangeAnythingElse = true + TimeUI.endTempus.dates.setValue(parsedNow) + } }) TimeUI.timeSlider.$on('stop', (e) => { - TimeUI.setCurrentTime(new Date(e.detail.value).toISOString(), false) + let idx = 0 + if (TimeUI.modes[TimeUI.modeIndex] === 'Point') idx -= 1 + + const date = new Date(e.detail.value) + const offsetNowDate = new Date( + date.getTime() + date.getTimezoneOffset() * 60000 + ) + if (e.detail.activeHandle === idx) { + const parsedNow = TimeUI.startTempus.dates.parseInput( + new Date(offsetNowDate) + ) + TimeUI.startTempus.dates.setValue(parsedNow) + } + if (e.detail.activeHandle === idx + 1) { + const parsedNow = TimeUI.endTempus.dates.parseInput( + new Date(offsetNowDate) + ) + TimeUI.endTempus.dates.setValue(parsedNow) + } }) if ($('#toggleTimeUI').hasClass('active') && ignoreHistogram !== true) TimeUI._makeHistogram() }, _makeHistogram() { - const startTimestamp = TimeUI._startTimestamp - const endTimestamp = TimeUI.getCurrentTimestamp() + const startTimestamp = TimeUI.removeOffset( + TimeUI._timelineStartTimestamp + ) + const endTimestamp = TimeUI.removeOffset(TimeUI._timelineEndTimestamp) // Don't remake if nothing changes if ( @@ -485,8 +722,10 @@ const TimeUI = { } }) - const starttimeISO = new Date(TimeUI._startTimestamp).toISOString() - const endtimeISO = new Date(endTimestamp).toISOString() + const starttimeISO = new Date( + TimeUI._timelineStartTimestamp + ).toISOString() + const endtimeISO = new Date(TimeUI._timelineEndTimestamp).toISOString() const NUM_BINS = Math.min(endTimestamp - startTimestamp, 360) const bins = new Array(NUM_BINS).fill(0) @@ -507,7 +746,9 @@ const TimeUI = { F_.linearScale( [startTimestamp, endTimestamp], [0, NUM_BINS], - new Date(time.t).getTime() + TimeUI.removeOffset( + new Date(time.t).getTime() + ) ) ) ]++ @@ -533,7 +774,7 @@ const TimeUI = { ) }) }, - _setCurrentTime(force, forceDate) { + _setCurrentTime(force, forceDate, disableChange) { if (TimeUI.now === true || force === true) { let date = forceDate || new Date() const offsetNowDate = new Date( @@ -542,8 +783,9 @@ const TimeUI = { const parsedNow = TimeUI.endTempus.dates.parseInput( new Date(offsetNowDate) ) - TimeUI.setCurrentTime(parsedNow) - TimeUI._remakeTimeSlider(true) + + TimeUI.setCurrentTime(parsedNow, disableChange) + //TimeUI._remakeTimeSlider(true) TimeUI.endTempus.dates.setValue(parsedNow) } }, @@ -553,9 +795,7 @@ const TimeUI = { // Start if (start != null) { date = new Date(start) - const offsetStartDate = new Date( - date.getTime() + date.getTimezoneOffset() * 60000 - ) + const offsetStartDate = TimeUI.addOffset(date) const parsedStart = TimeUI.startTempus.dates.parseInput( new Date(offsetStartDate) ) @@ -584,13 +824,22 @@ const TimeUI = { TimeUI.change() }, - setStartTime(ISOString, disableChange) { + setStartTime(ISOString, disableChange, dontRedrawTimeline) { const timestamp = Date.parse(ISOString) TimeUI._startTimestamp = timestamp + if (TimeUI._timelineStartTimestamp == null) + TimeUI._timelineStartTimestamp = TimeUI._startTimestamp - TimeUI._drawTimeLine() + if (dontRedrawTimeline != true) TimeUI._drawTimeLine() if (disableChange != true) TimeUI.change() }, + addOffset(timestamp) { + const date = new Date(timestamp) + const addedOffset = new Date( + date.getTime() + date.getTimezoneOffset() * 60000 + ) + return addedOffset + }, removeOffset(timestamp) { const date = new Date(timestamp) const removedOffset = new Date( @@ -616,6 +865,10 @@ const TimeUI = { TimeUI._endTimestamp === TimeUI.getCurrentTimestamp() const timestamp = Date.parse(ISOString) TimeUI._endTimestamp = timestamp + + if (TimeUI._timelineEndTimestamp == null) + TimeUI._timelineEndTimestamp = TimeUI._endTimestamp + TimeUI._drawTimeLine() if (sliderFixedToEnd) { @@ -623,30 +876,43 @@ const TimeUI = { if (disableChange != true) TimeUI.change() } }, - setCurrentTime(ISOString, disableChange, dontRemoveOffset) { + setCurrentTime( + ISOString, + disableChange, + dontRemoveOffset, + ignoreDontChange + ) { const timestamp = typeof ISOString === 'string' ? Date.parse(ISOString) : ISOString TimeUI._timeSliderTimestamp = timestamp - $('#mmgisTimeUICurrentTime').text( - moment - .utc( - TimeUI.getCurrentTimestamp( - dontRemoveOffset === true ? false : true - ) - ) - .format(FORMAT) - ) + if (TimeUI.play) { + const date = new Date(TimeUI._timeSliderTimestamp) + const offsetNowDate = new Date( + date.getTime() + date.getTimezoneOffset() * 60000 + ) + const parsedNow = TimeUI.endTempus.dates.parseInput( + new Date(offsetNowDate) + ) + if (ignoreDontChange !== true) + TimeUI.endTempus.dontChangeAnythingElse = true + TimeUI.endTempus.dates.setValue(parsedNow) + } + if (disableChange != true) TimeUI.change() }, change() { if ( typeof TimeUI.timeChange === 'function' && - TimeUI._startTimestamp && - TimeUI._endTimestamp + TimeUI._startTimestamp != null && + TimeUI._endTimestamp != null ) { + const mode = TimeUI.modes[TimeUI.modeIndex] + TimeUI.timeChange( new Date( - TimeUI.removeOffset(TimeUI._startTimestamp) + mode === 'Range' + ? TimeUI.removeOffset(TimeUI._startTimestamp) + : 0 ).toISOString(), new Date( TimeUI.removeOffset(TimeUI._endTimestamp) @@ -655,32 +921,67 @@ const TimeUI = { ) } }, - _drawTimeLine() { + _timelineDrag: function (e) { + if (TimeUI._timelineDragging === true) { + const dx = e.originalEvent.movementX + const width = document + .getElementById('mmgisTimeUITimelineInner') + .getBoundingClientRect().width + const dif = + TimeUI._timelineEndTimestamp - TimeUI._timelineStartTimestamp + + const nextStart = + TimeUI._timelineStartTimestamp - 0 - (dif / width) * dx + const nextEnd = + TimeUI._timelineEndTimestamp - 0 - (dif / width) * dx + + TimeUI._drawTimeLine(nextStart, nextEnd) + + clearTimeout(TimeUI._panHistoTimeout) + $('#mmgisTimeUITimelineHisto').empty() + TimeUI._panHistoTimeout = setTimeout(() => { + TimeUI._makeHistogram() + }, 3000) + } + }, + _drawTimeLine(forceStart, forceEnd) { const timelineElm = $('#mmgisTimeUITimelineInner') timelineElm.empty() - const s = TimeUI.removeOffset(TimeUI._startTimestamp) - const e = TimeUI.removeOffset(TimeUI._endTimestamp) + let s = forceStart || TimeUI._timelineStartTimestamp + let e = forceEnd || TimeUI._timelineEndTimestamp + if (e == null || s == null) return + s = Math.max(s - 0, 0) // Year 1970 + e = Math.min(e - 0, 3155788800000) // Year 2070 + + TimeUI._timelineStartTimestamp = parseInt(s) + TimeUI._timelineEndTimestamp = parseInt(e) + const dif = e - s let unit = null - if (dif / MS.year > 3) { + if (dif / MS.decade > 2) { + unit = 'decade' + } else if (dif / MS.year > 2) { unit = 'year' } else if (dif / MS.month > 1) { unit = 'month' - } else if (dif / MS.day > 2) { + } else if (dif / MS.day > 1.5) { unit = 'day' - } else if (dif / MS.hour > 1) { + } else if (dif / MS.hour > 0.75) { unit = 'hour' - } else if (dif / MS.minute > 1) { + } else if (dif / MS.minute > 0.75) { unit = 'minute' } else unit = 'second' let first = true const bigTicks = F_.getTimeStartsBetweenTimestamps(s, e, unit) + TimeUI._tickTippies.forEach((t) => { + if (t[0].state.isDestroyed != true) t[0].destroy() + }) for (let i = 0; i < bigTicks.length; i++) { const left = F_.linearScale([s, e], [0, 100], bigTicks[i].ts) if (left >= 0 && left <= 100) { @@ -688,15 +989,26 @@ const TimeUI = { [ `
`, `
`, - `
${ + `
${ bigTicks[i].label - }${first ? `
${unit}` : ''}${unit}` : ''}
`, `
`, ].join('\n') ) first = false + TimeUI._tickTippies.push( + tippy(`#mmgisTimeUITickLabel_${i}`, { + content: moment( + TimeUI.addOffset(new Date(bigTicks[i].ts)) + ).format(FORMAT), + placement: 'top', + theme: 'blue', + }) + ) } } + + TimeUI._remakeTimeSlider(true) }, } diff --git a/src/essence/Basics/Formulae_/Formulae_.js b/src/essence/Basics/Formulae_/Formulae_.js index a957f880..da42584a 100644 --- a/src/essence/Basics/Formulae_/Formulae_.js +++ b/src/essence/Basics/Formulae_/Formulae_.js @@ -59,6 +59,9 @@ var Formulae_ = { range[0] ) }, + getBase64Transparent256Tile: function () { + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAABFUlEQVR42u3BMQEAAADCoPVP7WsIoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAMBPAAB2ClDBAAAAABJRU5ErkJggg==' + }, monthNumberToName: function (monthNumber) { switch (monthNumber) { case 0: @@ -100,6 +103,23 @@ var Formulae_ = { let currentDate switch (unit) { + case 'decade': + currentDate = new Date( + Date.UTC(Math.floor(startDate.getFullYear() / 10) * 10) + ) + while (currentDate < endDate) { + currentDate.setUTCFullYear( + Math.floor(currentDate.getUTCFullYear() / 10) * 10 + 10 + ) + timeStarts.push({ + ts: Date.parse(currentDate), + label: + Math.floor(currentDate.getUTCFullYear() / 10) * 10 + + 's', + }) + } + + break case 'year': currentDate = new Date(Date.UTC(startDate.getFullYear())) while (currentDate < endDate) { @@ -121,7 +141,7 @@ var Formulae_ = { ts: Date.parse(currentDate), label: Formulae_.monthNumberToName( currentDate.getUTCMonth() - ), + ).toUpperCase(), }) } break diff --git a/src/essence/Basics/Layers_/LayerConstructors.js b/src/essence/Basics/Layers_/LayerConstructors.js index b2a68779..247c4fc7 100644 --- a/src/essence/Basics/Layers_/LayerConstructors.js +++ b/src/essence/Basics/Layers_/LayerConstructors.js @@ -219,6 +219,28 @@ export const constructVectorLayer = ( } layerObj.shape = 'directional_circle' } + /* + const markerXY = Map_.map.latLngToLayerPoint(latlong) + const markerLatLong = Map_.map.containerPointToLatLng([ + markerXY.x, + markerXY.y, + ]) + const pixelBelowMarkerLatLong = Map_.map.containerPointToLatLng( + [markerXY.x, markerXY.y + 1] + ) + console.log( + latlong, + markerXY, + markerLatLong, + pixelBelowMarkerLatLong, + F_.bearingBetweenTwoLatLngs( + pixelBelowMarkerLatLong.lat, + pixelBelowMarkerLatLong.lng, + markerLatLong.lat, + markerLatLong.lng + ) + ) + */ } switch (layerObj.shape) { diff --git a/src/essence/Basics/Layers_/leaflet-tilelayer-middleware.js b/src/essence/Basics/Layers_/leaflet-tilelayer-middleware.js index 262993d4..a00e2542 100644 --- a/src/essence/Basics/Layers_/leaflet-tilelayer-middleware.js +++ b/src/essence/Basics/Layers_/leaflet-tilelayer-middleware.js @@ -11,6 +11,8 @@ https://github.com/xtk93x/Leaflet.TileLayer.ColorFilter */ +import F_ from '../../Basics/Formulae_/Formulae_' + var colorFilterExtension = { intialize: function (url, options) { L.TileLayer.prototype.initialize.call(this, url, options) @@ -93,6 +95,37 @@ var colorFilterExtension = { this._container.style['mix-blend-mode'] = this.colorBlend() } }, + // Reduces tile flicker. This and refresh() from https://github.com/Leaflet/Leaflet/issues/6659#issuecomment-813328622 + _refreshTileUrl: function (tile, url) { + //use a image in background, so that only replace the actual tile, once image is loaded in cache! + let img = new Image() + img.onload = function (e) { + L.Util.requestAnimFrame(function () { + tile.el.src = url + tile.el.style.opacity = 1 + }) + } + img.onerror = function (e) { + tile.el.src = F_.getBase64Transparent256Tile() + tile.el.style.opacity = 0 + } + img.src = url + }, + refresh: function () { + if (this._map == null) return + + for (let key in this._tiles) { + const tile = this._tiles[key] + if (tile.current && tile.active) { + const oldsrc = tile.el.src + const newsrc = this.getTileUrl(tile.coords) + if (oldsrc != newsrc) { + //L.DomEvent.off(tile, 'load', this._tileOnLoad); ... this doesnt work! + this._refreshTileUrl(tile, newsrc) + } + } + } + }, } var wmsExtension = { @@ -257,5 +290,7 @@ L.tileLayer.colorFilter = function (url, options) { } url = url.replace(/{t}/g, '_time_') - return new L.TileLayer.ColorFilter(url, options) + const tileLayer = new L.TileLayer.ColorFilter(url, options) + + return tileLayer } diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 1a1e0271..15f8e423 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -860,6 +860,8 @@ async function makeLayer(layerObj, evenIfOff, forceGeoJSON) { continuousWorld: true, reuseTiles: true, bounds: bb, + timeEnabled: + layerObj.time != null && layerObj.time.enabled === true, time: typeof layerObj.time === 'undefined' ? '' : layerObj.time.end, compositeTile: typeof layerObj.time === 'undefined'