import L from 'leaflet' import './leaflet.bezier' const arcStyles = { edu: { color: 'rgb(245, 246, 150)', fillColor: 'rgb(245, 246, 150)', opacity: 0.5, weight: '1', }, company: { color: 'rgb(50, 100, 246)', fillColor: 'rgb(50, 100, 246)', opacity: 1.0, weight: '2', }, gov: { color: 'rgb(245, 150, 100)', fillColor: 'rgb(245, 150, 150)', opacity: 1.0, weight: '2', }, mil: { color: 'rgb(245, 0, 0)', fillColor: 'rgb(245, 0, 0)', opacity: 1.0, weight: '2', }, } const buttonOrder = ['edu', 'company', 'gov'] const sortOrder = ['edu', 'company', 'gov', 'mil'] const typeScores = { all: 0, edu: 1, company: 2, gov: 3, mil: 3 } const redDot = L.icon({ iconUrl: '/assets/img/reddot.png', iconSize: [17, 17], // size of the icon iconAnchor: [8, 8], // point of the icon which will correspond to marker's location popupAnchor: [0, -5] // point from which the popup should open relative to the iconAnchor }) class Map { constructor(el, data) { let { paper, citations } = data this.el = el this.paper = paper this.markers = [] this.arcs = [] this.filter = typeScores.all if (paper.addresses && paper.addresses.length) { this.source = [ paper.addresses[0].lat, paper.addresses[0].lng ].map(n => (parseFloat(n) || 0)) } else { console.error("No address found for root paper") this.source = [0, 0] // console.log(data) } // group papers by address this.citationsByAddress = {} this.citations = citations this.citations.forEach(citation => { if (!citation.addresses) { return } citation.addresses.forEach(address => { if (!(address.name in this.citationsByAddress)) { this.citationsByAddress[address.name] = { address, citations: [] } } this.citationsByAddress[address.name].citations.push(citation) }) }) this.build() this.bind() this.buildMarkers() this.rootMarker.openPopup() } build() { this.map = L.map(this.el).setView([25, 0], 2) L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { attribution: 'Map data © OpenStreetMap contributors, ' + 'CC-BY-SA, ' + 'Imagery © Mapbox', maxZoom: 18, id: 'mapbox.dark', style: 'mapbox://styles/mapbox/dark-v9', accessToken: 'pk.eyJ1IjoiZmFuc2FsY3kiLCJhIjoiY2pvN3I1czJwMHF5NDNrbWRoMWpteHlrdCJ9.kMpM5syQUhVjKkn1iVx9fg' }).addTo(this.map) } bind() { // a transparent div to cover the map, so normal scroll events will not be eaten by leaflet const mapCover = document.createElement("div") mapCover.classList.add("map_cover") mapCover.innerHTML = "
" mapCover.querySelector('div').addEventListener('click', () => { this.map.scrollWheelZoom.enable() if (mapCover.parentNode === this.el) { this.el.removeChild(mapCover) } }) mapCover.querySelector('div').addEventListener('touchstart', e => { e.stopPropagation() }) mapCover.querySelector('div').addEventListener('tap', e => { e.stopPropagation() this.map.scrollWheelZoom.enable() if (mapCover.parentNode === this.el) { this.el.removeChild(mapCover) } }) function stopPropagation(e) { e.stopPropagation() } mapCover.addEventListener('mousewheel', stopPropagation, true) mapCover.addEventListener('DOMMouseScroll', stopPropagation, true) this.map.scrollWheelZoom.disable() this.map.on('focus', () => { this.map.scrollWheelZoom.enable() if (mapCover.parentNode === this.el) { this.el.removeChild(mapCover) } }) this.map.on('blur', () => { this.map.scrollWheelZoom.disable() // el.appendChild(mapCover) }) this.el.appendChild(mapCover) buttonOrder.forEach(type => { const typeClass = type.substr(0, 3) const el = document.querySelector('.map-legend .' + typeClass) el.addEventListener('click', () => { console.log(el) this.filterMarkers(el, type) }) }) } filterMarkers(el, type) { const active = document.querySelector('.map-legend .active') if (active) active.classList.remove('active') const newFilter = typeScores[type] if (this.filter === newFilter) { this.filter = typeScores.all } else { this.filter = newFilter el.classList.add('active') } this.buildMarkers() } resetMarkers() { this.arcs.forEach(arc => arc.remove()) this.markers.forEach(marker => marker.remove()) this.markers = [] this.arcs = [] } buildMarkers() { this.resetMarkers() Object.keys(this.citationsByAddress).map(name => { const { citations: citationList, address } = this.citationsByAddress[name] if (this.filter && typeScores[address.type] !== this.filter) return // console.log(name, citationsByAddress[name]) // console.log(citation) const latlng = [address.lat, address.lng].map(n => parseFloat(n)) if (Number.isNaN(latlng[0]) || Number.isNaN(latlng[1])) return this.addMarker(latlng, citationList) const style = { ...arcStyles[address.type] } let weight = Math.min(citationList.length, 5) let opacity = 0.5 + Math.min(citationList.length / 5, 0.5) if (address.type !== 'edu') { weight += 1 opacity = 1 } style.weight = String(weight) style.opacity = opacity this.addArc(this.source, latlng, style) }) this.rootMarker = this.addMarker(this.source, [this.paper]) } addMarker(latlng, citations) { const marker = L.marker(latlng, { icon: redDot }).addTo(this.map) let message = citations.map(citation => { const { title, addresses, year, pdf, doi } = citation let rec = [ "", title, "", ] if (pdf && pdf.length) { rec.unshift("") rec.push("") } else if (doi && doi.length) { rec.unshift("") rec.push("") } if (year) { rec.push(" (" + year + ")") } const addressString = addresses.map(addr => addr.name).join('