@curupira/vue-mapbox
Version:
A wrapper for Mapbox using Vue, with advanced features and abstractions
526 lines (466 loc) • 14 kB
JavaScript
import lineSliceAlong from '@turf/line-slice-along'
import bbox from '@turf/bbox'
// import length from "@turf/length"
import center from '@turf/center-of-mass'
import _ from 'lodash'
import { TimelineMax } from 'gsap/TweenMax'
var turf = {
lineSliceAlong,
bbox,
center
}
export default {
name: 'layer',
render: function (createElement) {
return false
},
props: {
featureDataKey: {
default: 'geocod'
},
name: {
default: () => _.uniqueId('mapboxlayer')
},
zIndex: {
default: 1
},
legends: {
default: false // [ {color,value},{color,value} ]
},
geoJSON: {
default: false
},
mvt: {
default: false
},
sourceLayer: {
default: false
},
filter: {},
featureData: false,
layerStyle: {},
source: false,
zoomMin: {
default: 0
},
zoomMax: {
default: 22
},
icons: {} // {name:url,name2:url2}
},
// ['layerStyle','filter','featureData'],
data () {
return {
__name: '',
_geoJSON: false,
layerAdded: false,
// [0,14]
loadDelay: 0, // time to load layer
// icons: {}, //{name:url,name2:url2}
// tween: new TimelineMax().pause(),
obj: { total: 0 }, // this.$options.tween;
sourceOptions: {},
nameLayerFromData: undefined // if want to stecify a name for dinamic layer, set this attr in beforeCreate
}
},
// master to all layer to be used to controll.
// layer especific is set in computed properties
tweenMaster: new TimelineMax().pause(),
computed: {
// geoJSON:function(){
// return false;//this.$store.getters.kms;
// },
map: function () {
return window.mapboxmap
var parent = this.$parent
while (parent) {
if (_.hasIn(parent, 'map')) {
return parent.map
break
}
parent = parent.$parent
}
return false
},
sourceName: function () {
return _.get(this, 'nameLayerFromData', this.name) + '_source'
},
layerName: function () {
const name = _.get(this, 'nameLayerFromData', this.name) + '_layer'
return name
},
tween: function () {
return new TimelineMax().pause() // this.$options.tween;
},
beforeLayer: function () {
if (!this.map) return false
// var layers = this.$store.state.layers;
// var index = _.indexOf(layers,this.layerName);
// var layerBeforeName = layers[index+1] || "";
// var layers = this.$store.state.layersInfos;
// layers = _.orderBy(chars, ['name'],['asc']);;
// var index = _.indexOf(layers,this.layerName);
// var layerBeforeName = layers[index+1] || "";
const layerBeforeName = this.$store.getters.beforeLayer(this.layerName)
// get if layer exist
var layer = this.map.getLayer(layerBeforeName)
if (layer) {
return layerBeforeName
}
return ''
},
mapboxlayers: function () {
return this.$store.mapboxlayers
}
},
watch: {
geoJSON: function (val) {
this.tween.kill()
this.tween.clear()
this._geoJSON = val
setTimeout(this.setSource, 200)
},
layerStyle: function () {
this.setLayerStyle()
},
source: function () {
this.setSource()
},
filter: function () {
this.setLayerFilter()
},
featureData: function () {
this.setFeatureData()
},
zIndex: function (val) {
if (this.layerAdded) {
this.$store.commit('updateLayersInfos', {
name: this.layerName,
zIndex: this.layerIndex,
legends: this.legends
})
this.setLayerOrder()
}
},
beforeLayer: function () {
this.setLayerOrder()
},
mapboxlayers: function () {
this.setLayerOrder()
}
},
beforeCreate: function () {
this._geoJSON = false
},
created () {
this.$options.name = this.name
this.__name = this.name
},
mounted: function () {
if (!this.$options.name) throw Error('Name of layer not defined')
this._geoJSON = this.geoJSON
// load icons
// LETODO - check if error if not load icons after add to map
if (!_.isEmpty(this.icons)) {
_.each(this.icons, (v, k) => {
this.map.loadImage(v, (error, image) => {
if (error) console.warn(error)
this.map.addImage(k, image)
})
})
}
this.$store.commit('addLayer', this.layerName)
this.$store.commit('layersInfos', {
name: this.layerName,
zIndex: this.zIndex,
legends: this.legends
})
setTimeout(() => {
this.setSource()
this.addLayer()
}, this.loadDelay * 1000)
},
beforeDestroy: function () {
this.$store.commit('removeLayer', this.layerName)
if (this.map) {
this.map.removeLayer(this.layerName)
this.map.removeSource(this.sourceName)
// this.map.up
console.log('REMOVENDO:' + this.layerName)
}
},
methods: {
addLayer: function () {
if (this.map && !this.layerAdded) {
this.layerAdded = true
var layerStyle = this.getLayerStyle()
this.map.addLayer(layerStyle)
this.setLayerFilter()
this.setFeatureData()
this.setZoomRange()
this.setEvents()
// setTimeout(() => this.setLayerOrder(), 50); //set a little delay to make time to olhers layers to be created
setTimeout(() => this.setLayerOrder(), 500) // set a little delay to make time to olhers layers to be created
}
},
setEvents: function () {
var _t = this
this.map.on('click', this.layerName, function (e) {
e = { ...e, ...e.features[0].properties }
_t.$emit('click', e)
})
this.map.on('mousemove', this.layerName, function (e) {
// console.log('mousemove: '+this.layerName)
if (e.features.length > 0) {
e = { ...e, ...e.features[0].properties }
_t.$emit('mousemove', e)
}
})
this.map.on('mouseleave', this.layerName, function (e) {
// if(e.features.length > 0){
// e = {...e,...e.features[0].properties}
_t.$emit('mouseleave', e)
// }
})
},
getSourceDefinition: function () {
// check if source is in props, if so i consider it alone
if (this.$props.source) {
return this.$props.source
}
// first check for source properties
var sourceProperties = _.get(this.$options, 'source')
if (this.mvt) {
return {
type: 'vector',
tiles: [this.mvt],
minzoom: 0,
maxzoom: 20,
generateId: true
// 'source-layer':this.sourceLayer
}
}
if (this.geoJSON) {
return { type: 'geojson', data: this.geoJSON }
}
if (sourceProperties) {
if (_.isFunction(sourceProperties)) {
sourceProperties = sourceProperties.apply(this)
}
return { ...sourceProperties, ...this.sourceOptions }
// if not have, faalback to geoJson source
} else {
var data = this._geoJSON || geojsonTemplate
return { type: 'geojson', data: data, ...this.sourceOptions }
}
},
setSource: function () {
if (!this.map) return false
var sourceDefinition = this.getSourceDefinition()
var sourceObject = this.map.getSource(this.sourceName)
if (sourceObject) {
if (_.isFunction(sourceObject.setData) && sourceDefinition.data) { sourceObject.setData(sourceDefinition.data) }
} else {
delete sourceDefinition.id
// LETODO - poder ver se esta usando o mesmo source para todos poder mudar
// vejo se o source ja existe no mapa, se ja existe nao preciso colocar ele
this.map.addSource(this.sourceName, sourceDefinition)
}
},
setZoomRange: function () {
this.map.setLayerZoomRange(
this.layerName,
this.zoomMin,
this.zoomMax
)
},
setLayerOrder: function () {
if (!this.layerAdded) return false
var layerBeforeName = this.$store.getters.beforeLayer(this.layerName)
var layer = this.map.getLayer(layerBeforeName)
if (layer) {
if (layerBeforeName != '' && layerBeforeName != this.layerName) {
this.map.moveLayer(this.layerName, layerBeforeName)
} else {
this.map.moveLayer(this.layerName)
}
}
},
getLayerStyle: function () {
let styleOrigem
if (this.layerStyle) {
styleOrigem = this.layerStyle
} else {
styleOrigem = this.$options.style
}
var style
if (_.isFunction(styleOrigem)) {
style = styleOrigem.apply(this)
} else {
style = styleOrigem
}
if (this.sourceLayer) {
style['source-layer'] = this.sourceLayer
}
if (!style.source) style.source = this.sourceName
// if(!style.id)
style.id = this.layerName
if (!style.minzoom) style.minzoom = Number(this.zoomMin)
if (!style.maxzoom) style.maxzoom = Number(this.zoomMax)
return style
},
setLayerStyle: function () {
if (!this.map && this.layerAdded) return
console.log('update style for: ' + this.layerName)
const styleRoot = this.map.getStyle()
const layerIndex = _.findIndex(styleRoot.layers, { id: this.layerName })
const layerStyle = this.getLayerStyle()
styleRoot.layers[layerIndex] = layerStyle
this.map.setStyle(styleRoot)
// return style;
},
setLayerFilter: function () {
if (!this.map && this.layerAdded) return
this.map.setFilter(this.layerName, this.filter)
},
setFeatureData: function () {
if (!this.map && this.layerAdded) return
const keys = _.map(this.featureData, (v, k) => k)
// let features = this.map.querySourceFeatures(this.layerName, {
// this.mapboxmap.queryRenderedFeatures({layers:['regionalizacao-heatmap_layer'], filter:["match", ["get", "geocod"],["3300225","3172103"],true,false] });
// let features = this.map.querySourceFeatures(this.sourceName, {
// sourceLayer: this.layer,
// filter: ['in', this.featureDataKey, ...keys]
// });
// LETODO - nao funciona sem o vector ja vim com o id mesmo pelo jeito
// let features = this.map.queryRenderedFeatures({layers:[this.layerName],
// filter: ['in', this.featureDataKey, ...keys]
// });
// _.each(features,feature=>{
// feature.id = feature.properties.id
// this.map.setFeatureState(feature,{hover:true})
// })
},
animateLine: function (time, follow = false) {
if (!this.geoJSON) return false
var distance = 0
var geoJsonTemp
var distanceTotal = turf.length(this.geoJSON)
this.map.resize()
if (follow) {
var paddingHorizontal = 50 // window.innerWidth * 1 / 4;
var paddingVertical = 50 // window.innerHeight * 1 / 4;
var bbox = turf.bbox(this.geoJSON)
this.map.fitBounds(bbox, {
padding: {
top: paddingVertical,
bottom: paddingVertical,
left: paddingHorizontal,
right: paddingHorizontal
},
duration: 0.8 * time * 1000
})
}
var startDistane = 0
var obj = { distance: 0 }
// greensock for animation control
this.tween.to(obj, time, {
distance: distanceTotal,
onUpdate: () => {
if (obj.distance > distanceTotal) {
geoJsonTemp = this.geoJSON
} else {
geoJsonTemp = turf.lineSliceAlong(
this.geoJSON,
startDistane,
obj.distance
)
}
this._geoJSON = geoJsonTemp
this.setSource()
}
})
this.tween.play()
},
animateFeatures: function (time, follow = false) {
if (!_.hasIn(this.geoJSON, 'features')) return false
// get all corrds from json
var geoJsonTemp = { ...this.geoJSON }
var featuresOriginal = [...this.geoJSON.features]
var totalFeatures = featuresOriginal.length
var _t = this
var obj = { total: 0 }
this.tween.add(
TweenMax.to(obj, time, {
total: totalFeatures,
ease: SteppedEase.config(totalFeatures),
onUpdate: function () {
if (obj.total > totalFeatures) {
geoJsonTemp = { ..._t.geoJSON }
} else {
geoJsonTemp.features = [
...featuresOriginal.slice(0, parseInt(obj.total))
]
}
_t._geoJSON = geoJsonTemp
_t.setSource()
}
})
)
this.tween.play()
}
},
style: function () {
return {
type: 'symbol',
layout: {
'icon-image': 'marker_km',
'icon-anchor': 'bottom',
'icon-allow-overlap': false,
'text-field': '{name}',
'text-anchor': 'bottom',
'text-line-height': 1,
'text-optional': true,
'text-offset': {
base: 1,
stops: [[10, [0, -1]], [14, [0, -1]]]
},
'text-padding': 0,
'text-size': {
base: 1,
stops: [[10, 6], [14, 12]]
},
'icon-size': {
base: 1,
stops: [[9, 0.1], [10, 0.2], [14, 1]]
}
},
paint: {
'text-color': 'white',
'text-halo-color': 'white',
'text-halo-width': 0.4,
'text-halo-blur': 0.2,
'text-opacity': {
base: 1,
stops: [[11.4, 0], [13, 1]]
},
'icon-opacity': {
base: 1,
stops: [[11.3, 0], [11.4, 1]]
}
}
}
}
} // mixin end
var geojsonTemplate = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [[0, 0]]
}
}
]
}