Skip to content

Commit

Permalink
Merge pull request #1 from DavidMStraub/map_time_slider
Browse files Browse the repository at this point in the history
Add simple time slider to map view
  • Loading branch information
1ec5 authored Jul 2, 2024
2 parents eb0e7ab + 109615a commit 48de6cd
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 2 deletions.
12 changes: 10 additions & 2 deletions src/components/GrampsjsMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'maplibre-gl'
import '@maplibre/maplibre-gl-leaflet'
import './GrampsjsMapOverlay.js'
import './GrampsjsMapMarker.js'
import {fireEvent} from '../util.js'
import {fireEvent, filterByDecimalYear} from '../util.js'
import '../LocateControl.js'

const {L} = window
Expand Down Expand Up @@ -46,6 +46,7 @@ class GrampsjsMap extends LitElement {
width: {type: String},
latitude: {type: Number},
longitude: {type: Number},
year: {type: Number},
mapid: {type: String},
zoom: {type: Number},
latMin: {type: Number},
Expand All @@ -55,6 +56,7 @@ class GrampsjsMap extends LitElement {
layerSwitcher: {type: Boolean},
locateControl: {type: Boolean},
_map: {type: Object},
_glMap: {type: Object},
_layercontrol: {type: Object},
}
}
Expand All @@ -67,6 +69,7 @@ class GrampsjsMap extends LitElement {
this.mapid = 'mapid'
this.latitude = 0
this.longitude = 0
this.year = -1
this.latMin = 0
this.latMax = 0
this.longMin = 0
Expand Down Expand Up @@ -115,6 +118,7 @@ class GrampsjsMap extends LitElement {
position: 'bottomleft',
}
)
this._glMap = gl
if (this.layerSwitcher) {
this._map.addControl(this._layercontrol)
}
Expand All @@ -133,7 +137,11 @@ class GrampsjsMap extends LitElement {
}
}

updated() {
updated(changed) {
if (changed.has('year') && this.year > 0) {
filterByDecimalYear(this._glMap._glMap, this.year)
return
}
if (this._map !== undefined) {
if (this.latMin === 0 && this.latMax === 0) {
this._map.setZoom(this.zoom)
Expand Down
88 changes: 88 additions & 0 deletions src/components/GrampsjsMapTimeSlider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {html, css, LitElement} from 'lit'
import '@material/web/slider/slider.js'

import {sharedStyles} from '../SharedStyles.js'
import {GrampsjsTranslateMixin} from '../mixins/GrampsjsTranslateMixin.js'
import {fireEvent} from '../util.js'

class GrampsjsMapTimeSlider extends GrampsjsTranslateMixin(LitElement) {
static get styles() {
return [
sharedStyles,
css`
#container {
z-index: 999;
background-color: #ffffff;
border-radius: 14px;
width: calc(100% - 150px);
position: absolute;
bottom: 25px;
height: 28px;
left: 50%;
transform: translateX(-50%);
display: flex;
justify-content: center;
align-items: center;
}
md-slider {
width: 100%;
}
div.date {
display: inline-block;
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.6);
white-space: nowrap;
margin-left: 4px;
margin-right: 14px;
line-height: 22px;
height: 22px;
}
.date .year {
font-weight: 600;
}
`,
]
}

static get properties() {
return {
value: {type: Number},
}
}

constructor() {
super()
this.value = new Date().getFullYear() - 100
}

render() {
return html`
<div id="container">
<md-slider
@input="${this._handleInput}"
labeled
min="1500"
max="${new Date().getFullYear()}"
value="${this.value}"
></md-slider>
<div class="date">
<span class="year">${this.value}</span> &pm;
<span class="tolerance">10</span>
</div>
</div>
`
}

_handleInput() {
const slider = this.renderRoot.querySelector('md-slider')
const detail = {value: slider.value}
this.value = slider.value
fireEvent(this, 'timeslider:change', detail)
}
}

window.customElements.define('grampsjs-map-time-slider', GrampsjsMapTimeSlider)
117 changes: 117 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,3 +588,120 @@ export function dateIsEmpty(date) {
}
return true
}

// OpenHistoricalMap functions

/**
* Returns a `Date` object representing the given UTC date components.
*
* @param year A one-based year in the proleptic Gregorian calendar.
* @param month A zero-based month.
* @param day A one-based day.
* @returns A date object.
*/
function dateFromUTC(year, month, day) {
const date = new Date(Date.UTC(year, month, day))
// Date.UTC() treats a two-digit year as an offset from 1900.
date.setUTCFullYear(year)
return date
}

/**
* Converts the given ISO 8601-1 date to a decimal year.
*
* @param isoDate A date string in ISO 8601-1 format.
* @returns A floating point number of years since year 0.
*/
function decimalYearFromISODate(isoDate) {
// Require a valid YYYY, YYYY-MM, or YYYY-MM-DD date, but allow the year
// to be a variable number of digits or negative, unlike ISO 8601-1.
if (!isoDate || !/^-?\d{1,4}(?:-\d\d){0,2}$/.test(isoDate)) return undefined

const ymd = isoDate.split('-')
// A negative year results in an extra element at the beginning.
if (ymd[0] === '') {
ymd.shift()
ymd[0] *= -1
}
const year = +ymd[0]
const date = dateFromUTC(year, +ymd[1] - 1, +ymd[2])
if (Number.isNaN(date)) return undefined

// Add the year and the fraction of the date between two New Year’s Days.
const nextNewYear = dateFromUTC(year + 1, 0, 1).getTime()
const lastNewYear = dateFromUTC(year, 0, 1).getTime()
return year + (date.getTime() - lastNewYear) / (nextNewYear - lastNewYear)
}

/**
* Returns a modified version of the given filter that only evaluates to
* true if the feature coincides with the given decimal year.
*
* @param filter The original layer filter.
* @param decimalYear The decimal year to filter by.
* @returns A filter similar to the given filter, but with added conditions
* that require the feature to coincide with the decimal year.
*/
function constrainFilterByDate(filter, decimalYear) {
const newFilter = filter
if (filter && filter[0] === 'all' && filter[1] && filter[1][0] === 'any') {
if (
filter[1][2] &&
filter[1][2][0] === '<' &&
filter[1][2][1] === 'start_decdate'
) {
newFilter[1][2][2] = decimalYear + 1
}
if (
newFilter[2][2] &&
newFilter[2][2][0] === '>=' &&
newFilter[2][2][1] === 'end_decdate'
) {
newFilter[2][2][2] = decimalYear
}
return newFilter
}

const dateFilter = [
'all',
['any', ['!has', 'start_decdate'], ['<', 'start_decdate', decimalYear + 1]],
['any', ['!has', 'end_decdate'], ['>=', 'end_decdate', decimalYear]],
]
if (filter) {
dateFilter.push(filter)
}
return dateFilter
}

/**
* Filters the map’s features by the `date` data attribute.
*
* @param map The MapboxGL map object to filter the style of.
* @param year The numeric ear to filter by
*/
export function filterByDecimalYear(map, decimalYear) {
// eslint-disable-next-line array-callback-return
map.getStyle().layers.map(layer => {
if (!('source-layer' in layer)) return

const filter = constrainFilterByDate(map.getFilter(layer.id), decimalYear)
map.setFilter(layer.id, filter)
})
}

/**
* Filters the map’s features by the `date` data attribute.
*
* @param map The MapboxGL map object to filter the style of.
* @param date The date to filter by in YYYY-MM-DD format.
*/
export function filterByDate(map, dateP) {
let date = dateP
if (date === null || date === '') {
;[date] = new Date().toISOString().split('T')
}
const decimalYear = date && decimalYearFromISODate(date)
if (!decimalYear) return

filterByDecimalYear(map, decimalYear)
}
12 changes: 12 additions & 0 deletions src/views/GrampsjsViewMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {GrampsjsView} from './GrampsjsView.js'
import '../components/GrampsjsMap.js'
import '../components/GrampsjsMapMarker.js'
import '../components/GrampsjsMapSearchbox.js'
import '../components/GrampsjsMapTimeSlider.js'
import '../components/GrampsjsPlaceBox.js'
import {apiGet, getMediaUrl} from '../api.js'
import '@material/mwc-textfield'
Expand Down Expand Up @@ -76,6 +77,7 @@ export class GrampsjsViewMap extends GrampsjsView {
_valueSearch: {type: String},
_bounds: {type: Object},
_placeFilters: {type: Object},
_year: {type: Number},
}
}

Expand All @@ -90,6 +92,7 @@ export class GrampsjsViewMap extends GrampsjsView {
this._valueSearch = ''
this._bounds = {}
this._placeFilters = {}
this._year = -1
}

renderContent() {
Expand All @@ -102,6 +105,7 @@ export class GrampsjsViewMap extends GrampsjsView {
height="calc(100vh - 64px)"
latitude="${center[0]}"
longitude="${center[1]}"
year="${this._year}"
mapid="map-mapview"
@map:moveend="${this._handleMoveEnd}"
id="map"
Expand All @@ -119,6 +123,10 @@ export class GrampsjsViewMap extends GrampsjsView {
value="${this._valueSearch}"
>${this._renderPlaceDetails()}</grampsjs-map-searchbox
>
<grampsjs-map-time-slider
@timeslider:change="${this._handleTimeSliderChange}"
.strings="${this.strings}"
></grampsjs-map-time-slider>
`
}

Expand Down Expand Up @@ -154,6 +162,10 @@ export class GrampsjsViewMap extends GrampsjsView {
}
}

_handleTimeSliderChange(event) {
this._year = event.detail.value
}

_handlePlaceFilterChanged(event) {
this._placeFilters = {...event.detail}
this._applyPlaceFilter()
Expand Down

0 comments on commit 48de6cd

Please sign in to comment.