@advisr/v-mapbox
Version:
Mapbox with Vue 💚
2,134 lines (1,952 loc) • 55.8 kB
JavaScript
/*!
* @advisr/v-mapbox v1.12.0
* (c) 2022 GeoSpoc Dev Team
* @license MIT
*/
import Vue, { ref } from 'vue';
import promisify from 'map-promisified';
var layerEvents = [
'mousedown',
'mouseup',
'click',
'dblclick',
'mousemove',
'mouseenter',
'mouseleave',
'mouseover',
'mouseout',
'contextmenu',
'touchstart',
'touchend',
'touchcancel',
];
var withEventsMixin = Vue.extend({
methods: {
/**
* Emit Vue event with additional data
*
* @param {string} name EventName
* @param {object} [data={}] Additional data
*/
$_emitEvent(name, data = {}) {
this.$emit(name, {
map: this.map,
component: this,
...data,
});
},
/**
* Emit Vue event with Mapbox event as additional data
*
* @param {Record<string, any>} event - Event Payload
* @param {Record<string, any>} data - Data to be added to event
*/
$_emitMapEvent(event, data = {}) {
this.$_emitEvent(event.type, { mapboxEvent: event, ...data });
},
},
});
const mapboxSourceProps = {
sourceId: {
type: String,
required: true,
},
source: {
type: [Object, String],
default: undefined,
},
};
const mapboxLayerStyleProps = {
layerId: {
type: String,
required: true,
},
layer: {
type: Object,
required: true,
},
before: {
type: String,
default: undefined,
},
};
const componentProps = {
clearSource: {
type: Boolean,
default: true,
},
replaceSource: {
type: Boolean,
default: false,
},
replace: {
type: Boolean,
default: false,
},
};
var layerMixin = Vue.extend({
mixins: [withEventsMixin],
inject: ['mapbox', 'map'],
props: {
...mapboxSourceProps,
...mapboxLayerStyleProps,
...componentProps,
},
data() {
return {
initial: true,
};
},
computed: {
sourceLoaded() {
return this.map ? this.map.isSourceLoaded(this.sourceId) : false;
},
mapLayer() {
return this.map ? this.map.getLayer(this.layerId) : null;
},
mapSource() {
return this.map ? this.map.getSource(this.sourceId) : null;
},
},
watch: {
before(layer, oldLayer) {
if (layer !== oldLayer) {
this.move(layer);
}
},
},
created() {
if (this.layer.minzoom) {
this.$watch('layer.minzoom', function (next) {
if (this.initial) return;
this.map.setLayerZoomRange(this.layerId, next, this.layer.maxzoom);
});
}
if (this.layer.maxzoom) {
this.$watch('layer.maxzoom', function (next) {
if (this.initial) return;
this.map.setLayerZoomRange(this.layerId, this.layer.minzoom, next);
});
}
if (this.layer.paint) {
this.$watch(
'layer.paint',
function (next) {
if (this.initial) return;
if (next) {
for (const prop of Object.keys(next)) {
this.map.setPaintProperty(this.layerId, prop, next[prop]);
}
}
},
{ deep: true },
);
}
if (this.layer.layout) {
this.$watch(
'layer.layout',
function (next) {
if (this.initial) return;
if (next) {
for (const prop of Object.keys(next)) {
this.map.setLayoutProperty(this.layerId, prop, next[prop]);
}
}
},
{ deep: true },
);
}
if (this.layer.filter) {
this.$watch(
'layer.filter',
function (next) {
if (this.initial) return;
this.map.setFilter(this.layerId, next);
},
{ deep: true },
);
}
},
beforeDestroy() {
if (this.map && this.map.loaded()) {
try {
this.map.removeLayer(this.layerId);
} catch (err) {
this.$_emitEvent('layer-does-not-exist', {
layerId: this.sourceId,
error: err,
});
}
if (this.clearSource) {
try {
this.map.removeSource(this.sourceId);
} catch (err) {
this.$_emitEvent('source-does-not-exist', {
sourceId: this.sourceId,
error: err,
});
}
}
}
},
methods: {
$_emitLayerMapEvent(event) {
return this.$_emitMapEvent(event, { layerId: this.layerId });
},
$_bindLayerEvents(events) {
Object.keys(this.$listeners).forEach((eventName) => {
if (events.includes(eventName)) {
this.map.on(eventName, this.layerId, this.$_emitLayerMapEvent);
}
});
},
$_unbindEvents(events) {
if (this.map) {
events.forEach((eventName) => {
this.map.off(eventName, this.layerId, this.$_emitLayerMapEvent);
});
}
},
$_watchSourceLoading(data) {
if (data.dataType === 'source' && data.sourceId === this.sourceId) {
this.$_emitEvent('layer-source-loading', { sourceId: this.sourceId });
this.map.off('dataloading', this.$_watchSourceLoading);
}
},
move(beforeId) {
this.map.moveLayer(this.layerId, beforeId);
this.$_emitEvent('layer-moved', {
layerId: this.layerId,
beforeId: beforeId,
});
},
remove() {
this.map.removeLayer(this.layerId);
this.map.removeSource(this.sourceId);
this.$_emitEvent('layer-removed', { layerId: this.layerId });
this.$destroy();
},
},
render() {},
});
var CanvasLayer = Vue.extend({
name: 'MglCanvasLayer',
mixins: [layerMixin],
inject: ['mapbox', 'map'],
props: {
source: {
type: Object,
required: true,
},
layer: {
type: Object,
default: null,
},
},
computed: {
canvasElement() {
return this.mapSource ? this.mapSource.getCanvas() : null;
},
},
watch: {
coordinates(val) {
if (this.initial) return;
this.mapSource.setCoordinates(val);
},
},
created() {
this.$_deferredMount();
},
methods: {
$_deferredMount() {
const source = {
type: 'canvas',
...this.source,
};
this.map.on('dataloading', this.$_watchSourceLoading);
try {
this.map.addSource(this.sourceId, source);
} catch (err) {
if (this.replaceSource) {
this.map.removeSource(this.sourceId);
this.map.addSource(this.sourceId, source);
}
}
this.$_addLayer();
this.$_bindLayerEvents(layerEvents);
this.initial = false;
},
$_addLayer() {
const existed = this.map.getLayer(this.layerId);
if (existed) {
if (this.replace) {
this.map.removeLayer(this.layerId);
} else {
this.$_emitEvent('layer-exists', { layerId: this.layerId });
return existed;
}
}
const layer = {
id: this.layerId,
source: this.sourceId,
type: 'raster',
...this.layer,
};
this.map.addLayer(layer, this.before);
this.$_emitEvent('added', {
layerId: this.layerId,
canvas: this.canvasElement,
});
},
},
});
var GeojsonLayer = Vue.extend({
name: 'MglGeojsonLayer',
mixins: [layerMixin],
computed: {
getSourceFeatures() {
return (filter) => {
if (this.map) {
return this.map.querySourceFeatures(this.sourceId, { filter });
}
return null;
};
},
getRenderedFeatures() {
return (geometry, filter) => {
if (this.map) {
return this.map.queryRenderedFeatures(geometry, {
layers: [this.layerId],
filter,
});
}
return null;
};
},
getClusterExpansionZoom() {
return (clusterId) => {
return new Promise((resolve, reject) => {
if (this.mapSource) {
this.mapSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) {
return reject(err);
}
return resolve(zoom);
});
} else {
return reject(
new Error(`Map source with id ${this.sourceId} not found.`),
);
}
});
};
},
getClusterChildren() {
return (clusterId) => {
return new Promise((resolve, reject) => {
const source = this.mapSource;
if (source) {
source.getClusterChildren(clusterId, (err, features) => {
if (err) {
return reject(err);
}
return resolve(features);
});
} else {
return reject(
new Error(`Map source with id ${this.sourceId} not found.`),
);
}
});
};
},
getClusterLeaves() {
return (...args) => {
return new Promise((resolve, reject) => {
if (this.mapSource) {
this.mapSource.getClusterLeaves(...args, (err, features) => {
if (err) {
return reject(err);
}
return resolve(features);
});
} else {
return reject(
new Error(`Map source with id ${this.sourceId} not found.`),
);
}
});
};
},
},
created() {
if (this.source) {
this.$watch(
'source.data',
function (next) {
if (this.initial) return;
this.mapSource.setData(next);
},
{ deep: true },
);
}
this.$_deferredMount();
},
methods: {
$_deferredMount() {
// this.map = payload.map;
this.map.on('dataloading', this.$_watchSourceLoading);
if (this.source) {
const source = {
type: 'geojson',
...this.source,
};
try {
this.map.addSource(this.sourceId, source);
} catch (err) {
if (this.replaceSource) {
this.map.removeSource(this.sourceId);
this.map.addSource(this.sourceId, source);
}
}
}
this.$_addLayer();
this.$_bindLayerEvents(layerEvents);
this.map.off('dataloading', this.$_watchSourceLoading);
this.initial = false;
},
$_addLayer() {
const existed = this.map.getLayer(this.layerId);
if (existed) {
if (this.replace) {
this.map.removeLayer(this.layerId);
} else {
this.$_emitEvent('layer-exists', { layerId: this.layerId });
return existed;
}
}
const layer = {
id: this.layerId,
source: this.sourceId,
...this.layer,
};
this.map.addLayer(layer, this.before);
this.$_emitEvent('added', { layerId: this.layerId });
},
setFeatureState(featureId, state) {
if (this.map) {
const params = { id: featureId, source: this.source };
return this.map.setFeatureState(params, state);
}
},
getFeatureState(featureId) {
if (this.map) {
const params = { id: featureId, source: this.source };
return this.map.getFeatureState(params);
}
},
removeFeatureState(featureId, sourceLayer, key) {
if (this.map) {
const params = {
id: featureId,
source: this.source,
sourceLayer,
};
return this.map.removeFeatureState(params, key);
}
},
},
});
var ImageLayer = Vue.extend({
name: 'MglImageLayer',
mixins: [layerMixin],
created() {
if (this.source) {
if (this.source.coordinates) {
this.$watch(
'source.coordinates',
function (next) {
if (this.initial) return;
if (next) {
this.mapSource.setCoordinates(next);
}
},
{ deep: true },
);
}
if (this.source.url) {
this.$watch(
'source.url',
function (next) {
if (this.initial) return;
if (next) {
this.mapSource.updateImage({
url: next,
coordinates: this.source.coordinates,
});
}
},
{ deep: true },
);
}
}
this.$_deferredMount();
},
methods: {
$_deferredMount() {
const source = {
type: 'image',
...this.source,
};
this.map.on('dataloading', this.$_watchSourceLoading);
try {
this.map.addSource(this.sourceId, source);
} catch (err) {
if (this.replaceSource) {
this.map.removeSource(this.sourceId);
this.map.addSource(this.sourceId, source);
}
}
this.$_addLayer();
this.$_bindLayerEvents(layerEvents);
this.initial = false;
},
$_addLayer() {
const existed = this.map.getLayer(this.layerId);
if (existed) {
if (this.replace) {
this.map.removeLayer(this.layerId);
} else {
this.$_emitEvent('layer-exists', { layerId: this.layerId });
return existed;
}
}
const layer = {
id: this.layerId,
source: this.sourceId,
type: 'raster',
...this.layer,
};
this.map.addLayer(layer, this.before);
this.$_emitEvent('added', { layerId: this.layerId });
},
},
});
var RasterLayer = Vue.extend({
name: 'MglRasterLayer',
mixins: [layerMixin],
created() {
this.$_deferredMount();
},
methods: {
$_deferredMount() {
const source = {
type: 'raster',
...this.source,
};
this.map.on('dataloading', this.$_watchSourceLoading);
try {
this.map.addSource(this.sourceId, source);
} catch (err) {
if (this.replaceSource) {
this.map.removeSource(this.sourceId);
this.map.addSource(this.sourceId, source);
}
}
this.$_addLayer();
this.$_bindLayerEvents(layerEvents);
this.map.off('dataloading', this.$_watchSourceLoading);
this.initial = false;
},
$_addLayer() {
const existed = this.map.getLayer(this.layerId);
if (existed) {
if (this.replace) {
this.map.removeLayer(this.layerId);
} else {
this.$_emitEvent('layer-exists', { layerId: this.layerId });
return existed;
}
}
const layer = {
id: this.layerId,
type: 'raster',
source: this.sourceId,
...this.layer,
};
this.map.addLayer(layer, this.before);
this.$_emitEvent('added', { layerId: this.layerId });
},
},
});
var VectorLayer = Vue.extend({
name: 'MglVectorLayer',
mixins: [layerMixin],
computed: {
getSourceFeatures() {
return (filter) => {
if (this.map) {
return this.map.querySourceFeatures(this.sourceId, {
sourceLayer: this.layer['source-layer'],
filter,
});
}
return null;
};
},
getRenderedFeatures() {
return (geometry, filter) => {
if (this.map) {
return this.map.queryRenderedFeatures(geometry, {
layers: [this.layerId],
filter,
});
}
return null;
};
},
},
watch: {
filter(filter) {
if (this.initial) return;
this.map.setFilter(this.layerId, filter);
},
},
created() {
this.$_deferredMount();
},
methods: {
$_deferredMount() {
const source = {
type: 'vector',
...this.source,
};
this.map.on('dataloading', this.$_watchSourceLoading);
try {
this.map.addSource(this.sourceId, source);
} catch (err) {
if (this.replaceSource) {
this.map.removeSource(this.sourceId);
this.map.addSource(this.sourceId, source);
}
}
this.$_addLayer();
this.$_bindLayerEvents(layerEvents);
this.map.off('dataloading', this.$_watchSourceLoading);
this.initial = false;
},
$_addLayer() {
const existed = this.map.getLayer(this.layerId);
if (existed) {
if (this.replace) {
this.map.removeLayer(this.layerId);
} else {
this.$_emitEvent('layer-exists', { layerId: this.layerId });
return existed;
}
}
const layer = {
id: this.layerId,
source: this.sourceId,
...this.layer,
};
this.map.addLayer(layer, this.before);
this.$_emitEvent('added', { layerId: this.layerId });
},
setFeatureState(featureId, state) {
if (this.map) {
const params = {
id: featureId,
source: this.sourceId,
'source-layer': this.layer['source-layer'],
};
return this.map.setFeatureState(params, state);
}
},
getFeatureState(featureId) {
if (this.map) {
const params = {
id: featureId,
source: this.source,
'source-layer': this.layer['source-layer'],
};
return this.map.getFeatureState(params);
}
},
},
});
var VideoLayer = Vue.extend({
name: 'MglVideoLayer',
mixins: [layerMixin],
computed: {
video() {
return this.map.getSource(this.sourceId).getVideo();
},
},
created() {
if (this.source && this.source.coordinates) {
this.$watch('source.coordinates', function (next) {
if (this.initial) return;
this.mapSource.setCoordinates(next);
});
}
this.$_deferredMount();
},
methods: {
$_deferredMount() {
const source = {
type: 'video',
...this.source,
};
this.map.on('dataloading', this.$_watchSourceLoading);
try {
this.map.addSource(this.sourceId, source);
} catch (err) {
if (this.replaceSource) {
this.map.removeSource(this.sourceId);
this.map.addSource(this.sourceId, source);
}
}
this.$_addLayer();
this.$_bindLayerEvents(layerEvents);
this.initial = false;
},
$_addLayer() {
const existed = this.map.getLayer(this.layerId);
if (existed) {
if (this.replace) {
this.map.removeLayer(this.layerId);
} else {
this.$_emitEvent('layer-exists', { layerId: this.layerId });
return existed;
}
}
const layer = {
id: this.layerId,
source: this.sourceId,
type: 'background',
...this.layer,
};
this.map.addLayer(layer, this.before);
this.$_emitEvent('added', { layerId: this.layerId });
},
},
});
var mapEvents = {
boxzoomcancel: { name: 'boxzoomcancel' },
boxzoomend: { name: 'boxzoomcancel' },
boxzoomstart: { name: 'boxzoomstart' },
click: { name: 'click' },
contextmenu: { name: 'contextmenu' },
data: { name: 'data' },
dataloading: { name: 'dataloading' },
dblclick: { name: 'dblclick' },
drag: { name: 'drag' },
dragend: { name: 'dragend' },
dragstart: { name: 'dragstart' },
error: { name: 'error' },
idle: { name: 'idle' },
load: { name: 'load' },
mousedown: { name: 'mousedown' },
mouseenter: { name: 'mouseenter' },
mouseleave: { name: 'mouseleave' },
mousemove: { name: 'mousemove' },
mouseout: { name: 'mouseout' },
mouseover: { name: 'mouseover' },
mouseup: { name: 'mouseup' },
move: { name: 'move' },
moveend: { name: 'moveend' },
movestart: { name: 'movestart' },
pitch: { name: 'pitch' },
pitchend: { name: 'pitchend' },
pitchstart: { name: 'pitchstart' },
remove: { name: 'remove' },
render: { name: 'render' },
resize: { name: 'resize' },
rotate: { name: 'rotate' },
rotateend: { name: 'rotateend' },
rotatestart: { name: 'rotatestart' },
sourcedata: { name: 'sourcedata' },
sourcedataloading: { name: 'sourcedataloading' },
styledata: { name: 'styledata' },
styledataloading: { name: 'styledataloading' },
styleimagemissing: { name: 'styleimagemissing' },
touchcancel: { name: 'touchcancel' },
touchend: { name: 'touchend' },
touchmove: { name: 'touchmove' },
touchstart: { name: 'touchstart' },
webglcontextlost: { name: 'webglcontextlost' },
webglcontextrestored: { name: 'webglcontextrestored' },
wheel: { name: 'wheel' },
zoom: { name: 'zoom' },
zoomend: { name: 'zoomend' },
zoomstart: { name: 'zoomstart' },
};
const props = {
antialias: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
attributionControl: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
bearing: {
type: [Number, undefined] ,
default: 0,
required: false,
},
bearingSnap: {
type: [Number, undefined] ,
default: 7,
required: false,
},
bounds: {
type: [Object, Array, undefined] ,
default: undefined,
required: false,
},
boxZoom: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
center: {
type: [Object, Array, undefined] ,
default: undefined,
required: false,
},
clickTolerance: {
type: [Number, undefined] ,
default: 3,
required: false,
},
collectResourceTiming: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
crossSourceCollisions: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
container: {
type: String ,
required: false,
default: () => `map-${('' + Math.random()).split('.')[1]}`,
},
customAttribution: {
type: [String, Array, undefined] ,
default: null,
required: false,
},
dragPan: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
dragRotate: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
doubleClickZoom: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
hash: {
type: [Boolean, String, undefined]
,
default: false,
required: false,
},
fadeDuration: {
type: [Number, undefined] ,
default: 300,
required: false,
},
failIfMajorPerformanceCaveat: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
fitBoundsOptions: {
type: [Object, undefined] ,
default: undefined,
required: false,
},
interactive: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
keyboard: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
locale: {
type: [Object, undefined]
,
default: undefined,
required: false,
},
localIdeographFontFamily: {
type: [String, undefined] ,
default: 'sans-serif',
required: false,
},
logoPosition: {
type: [String, undefined] ,
default: 'bottom-left',
validator: (val) =>
['top-left', 'top-right', 'bottom-left', 'bottom-right'].includes(val),
required: false,
},
maxBounds: {
type: [Array, Object, undefined] ,
default: undefined,
required: false,
},
maxPitch: {
type: [Number, undefined] ,
default: 0,
required: false,
},
maxZoom: {
type: [Number, undefined] ,
default: 22,
required: false,
},
minPitch: {
type: [Number, undefined] ,
default: 0,
required: false,
},
minZoom: {
type: [Number, undefined] ,
default: 0,
required: false,
},
preserveDrawingBuffer: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
pitch: {
type: [Number, undefined] ,
default: 0,
required: false,
},
pitchWithRotate: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
refreshExpiredTiles: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
renderWorldCopies: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
scrollZoom: {
type: [Boolean, undefined] ,
default: () => true,
required: false,
},
mapStyle: {
type: [String, Object, undefined] ,
default: undefined,
required: true,
},
trackResize: {
type: [Boolean, undefined] ,
default: true,
required: false,
},
transformRequest: {
type: [Function, undefined]
,
default: undefined,
required: false,
},
touchZoomRotate: {
type: [Boolean, undefined] ,
default: () => true,
required: false,
},
touchPitch: {
type: [Boolean, undefined] ,
default: () => true,
required: false,
},
zoom: {
type: [Number, undefined] ,
default: 0,
required: false,
},
maxTileCacheSize: {
type: [Number, undefined] ,
default: null,
required: false,
},
accessToken: {
type: [String, undefined] ,
default: undefined,
required: false,
},
/**
* Reference(mapbox): https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-rtl-text/
* Reference(v-mapbox): ./GlMap.vue#L89
*/
RTLTextPluginUrl: {
type: [String, undefined] ,
default: undefined,
required: false,
},
/**
* Reference(mapbox): https://docs.mapbox.com/mapbox-gl-js/api/map/#map#setlight
* Reference(v-mapbox): ./mixins/withWatchers.js#L43
*/
light: {
type: [Object, undefined] ,
default: undefined,
required: false,
},
/**
* Reference(mapbox): https://docs.mapbox.com/mapbox-gl-js/api/map/#map#showtileboundaries
* Reference(v-mapbox): ./mixins/withWatchers.js#L25
*/
tileBoundaries: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
/**
* Reference(mapbox): https://docs.mapbox.com/mapbox-gl-js/api/map/#map#showcollisionboxes
* Reference(v-mapbox): ./mixins/withWatchers.js#L22
*/
collisionBoxes: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
/**
* Reference(mapbox): https://docs.mapbox.com/mapbox-gl-js/api/map/#map#repaint
* Reference(v-mapbox): ./mixins/withWatchers.js#L28
*/
repaint: {
type: [Boolean, undefined] ,
default: false,
required: false,
},
};
var options = props;
const watchers = {
maxBounds(next) {
this.map.setMaxBounds(next);
},
minZoom(next) {
this.map.setMinZoom(next);
},
maxZoom(next) {
this.map.setMaxZoom(next);
},
minPitch(next) {
this.map.setMinPitch(next);
},
maxPitch(next) {
this.map.setMaxPitch(next);
},
mapStyle(next) {
this.map.setStyle(next);
},
// TODO: make 'bounds' synced prop
// bounds (next) { this.map.fitBounds(next, { linear: true, duration: 0 }) },
collisionBoxes(next) {
this.map.showCollisionBoxes = next;
},
tileBoundaries(next) {
this.map.showTileBoundaries = next;
},
repaint(next) {
this.map.repaint = next;
},
zoom(next) {
this.map.setZoom(next);
},
center(next) {
this.map.setCenter(next);
},
bearing(next) {
this.map.setBearing(next);
},
pitch(next) {
this.map.setPitch(next);
},
light(next) {
this.map.setLight(next);
},
};
/**
* @param {object} prop - property name
* @param {Function} callback - callback function
* @param {object} next - next value
* @param {object} prev - previous value
*/
function watcher(prop, callback, next, prev) {
if (this.initial) return;
if (this.$listeners[`update:${prop}`]) {
if (this.propsIsUpdating[prop]) {
this._watcher.active = false;
this.$nextTick(() => {
this._watcher.active = true;
});
} else {
this._watcher.active = true;
callback(next, prev);
}
this.propsIsUpdating[prop] = false;
} else {
callback(next, prev);
}
}
/**
* @returns {object} wrapper
*/
function makeWatchers() {
const wrappers = {};
Object.entries(watchers).forEach((prop) => {
wrappers[prop[0]] = function (next, prev) {
return watcher.call(this, prop[0], prop[1].bind(this), next, prev);
};
});
return wrappers;
}
var withWatchers = Vue.extend({
watch: makeWatchers(),
});
var withPrivateMethods = Vue.extend({
setup() {
const container
= ref();
return { container };
},
methods: {
$_updateSyncedPropsFabric(prop, data) {
return () => {
this.propsIsUpdating[prop] = true;
const info = typeof data === 'function' ? data() : data;
return this.$emit(`update:${prop}`, info);
};
},
$_bindPropsUpdateEvents() {
const syncedProps = [
{
events: ['moveend'],
prop: 'center',
getter: this.map.getCenter.bind(this.map),
},
{
events: ['zoomend'],
prop: 'zoom',
getter: this.map.getZoom.bind(this.map),
},
{
events: ['rotate'],
prop: 'bearing',
getter: this.map.getBearing.bind(this.map),
},
{
events: ['pitch'],
prop: 'pitch',
getter: this.map.getPitch.bind(this.map),
},
{
events: ['moveend', 'zoomend', 'rotate', 'pitch'],
prop: 'bounds',
getter: () => {
let newBounds = this.map.getBounds();
if (this.$props.bounds instanceof Array) {
newBounds = newBounds.toArray();
}
return newBounds;
},
},
];
syncedProps.forEach(({ events, prop, getter }) => {
events.forEach((event) => {
if (this.$listeners[`update:${prop}`]) {
this.map.on(event, this.$_updateSyncedPropsFabric(prop, getter));
}
});
});
},
$_loadMap() {
return this.mapboxPromise.then((mapbox) => {
this.mapbox = mapbox.default ? mapbox.default : mapbox;
return new Promise((resolve) => {
if (this.accessToken) this.mapbox.accessToken = this.accessToken;
const map = new this.mapbox.Map({
...this._props,
container: this.container,
style: this.mapStyle,
});
map.on('load', () => resolve(map));
});
});
},
$_RTLTextPluginError(error) {
this.$emit('rtl-plugin-error', { map: this.map, error: error });
},
$_bindMapEvents(events) {
Object.keys(this.$listeners).forEach((eventName) => {
if (events.includes(eventName)) {
this.map.on(eventName, this.$_emitMapEvent);
}
});
},
$_unbindEvents(events) {
events.forEach((eventName) => {
this.map.off(eventName, this.$_emitMapEvent);
});
},
},
});
var withAsyncActions = Vue.extend({
created() {
this.actions = {};
},
methods: {
$_registerAsyncActions(map) {
this.actions = {
...promisify(map),
stop() {
this.map.stop();
const updatedProps = {
pitch: this.map.getPitch(),
zoom: this.map.getZoom(),
bearing: this.map.getBearing(),
center: this.map.getCenter(),
};
Object.entries(updatedProps).forEach((prop) => {
this.$_updateSyncedPropsFabric(prop[0], prop[1])();
});
return Promise.resolve(updatedProps);
},
};
},
},
});
var script$2 = Vue.extend({
name: 'MglMap',
mixins: [withWatchers, withAsyncActions, withPrivateMethods, withEventsMixin],
provide() {
const self = this;
return {
get mapbox() {
return self.mapbox;
},
get map() {
return self.map;
},
get actions() {
return self.actions;
},
};
},
props: {
mapboxGl: {
type: Object,
default: null,
},
...options,
},
emits: ['load', ...Object.keys(mapEvents)],
data() {
return {
initial: true,
initialized: false,
};
},
computed: {
loaded() {
return this.map ? this.map.loaded() : false;
},
version() {
return this.map ? this.map.version : null;
},
isStyleLoaded() {
return this.map ? this.map.isStyleLoaded() : false;
},
areTilesLoaded() {
return this.map ? this.map.areTilesLoaded() : false;
},
isMoving() {
return this.map ? this.map.isMoving() : false;
},
canvas() {
return this.map ? this.map.getCanvas() : null;
},
canvasContainer() {
return this.map ? this.map.getCanvasContainer() : null;
},
images() {
return this.map ? this.map.listImages() : null;
},
},
created() {
this.map = null;
this.propsIsUpdating = {};
this.mapboxPromise = this.mapboxGl
? Promise.resolve(this.mapboxGl)
: import('mapbox-gl');
},
mounted() {
this.$_loadMap().then((map) => {
this.map = map;
if (
this.RTLTextPluginUrl !== undefined &&
this.mapbox.getRTLTextPluginStatus() !== 'loaded'
) {
this.mapbox.setRTLTextPlugin(
this.RTLTextPluginUrl,
this.$_RTLTextPluginError,
);
}
const eventNames = Object.keys(mapEvents);
this.$_bindMapEvents(eventNames);
this.$_registerAsyncActions(map);
this.$_bindPropsUpdateEvents();
this.initial = false;
this.initialized = true;
this.$emit('load', { map, component: this });
});
},
beforeDestroy() {
this.$nextTick(() => {
if (this.map) this.map.remove();
});
},
});
function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
if (typeof shadowMode !== 'boolean') {
createInjectorSSR = createInjector;
createInjector = shadowMode;
shadowMode = false;
}
// Vue.extend constructor export interop.
const options = typeof script === 'function' ? script.options : script;
// render functions
if (template && template.render) {
options.render = template.render;
options.staticRenderFns = template.staticRenderFns;
options._compiled = true;
// functional template
if (isFunctionalTemplate) {
options.functional = true;
}
}
// scopedId
if (scopeId) {
options._scopeId = scopeId;
}
let hook;
if (moduleIdentifier) {
// server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__;
}
// inject component styles
if (style) {
style.call(this, createInjectorSSR(context));
}
// register component module identifier for async chunk inference
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier);
}
};
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook;
}
else if (style) {
hook = shadowMode
? function (context) {
style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));
}
: function (context) {
style.call(this, createInjector(context));
};
}
if (hook) {
if (options.functional) {
// register for functional component in vue file
const originalRender = options.render;
options.render = function renderWithStyleInjection(h, context) {
hook.call(context);
return originalRender(h, context);
};
}
else {
// inject component registration as beforeCreate hook
const existing = options.beforeCreate;
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
}
}
return script;
}
/* script */
const __vue_script__$2 = script$2;
/* template */
var __vue_render__$2 = function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div",
{ staticClass: "mgl-map-wrapper" },
[_vm._m(0), _vm._v(" "), _vm.initialized ? _vm._t("default") : _vm._e()],
2
)
};
var __vue_staticRenderFns__$2 = [
function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c("div", { ref: "container", attrs: { id: _vm.container } })
},
];
__vue_render__$2._withStripped = true;
/* style */
const __vue_inject_styles__$2 = undefined;
/* scoped */
const __vue_scope_id__$2 = undefined;
/* module identifier */
const __vue_module_identifier__$2 = undefined;
/* functional template */
const __vue_is_functional_template__$2 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__$2 = /*#__PURE__*/normalizeComponent(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
__vue_script__$2,
__vue_scope_id__$2,
__vue_is_functional_template__$2,
__vue_module_identifier__$2,
false,
undefined,
undefined,
undefined
);
var GlMap = __vue_component__$2;
var withSelfEventsMixin = Vue.extend({
methods: {
$_emitSelfEvent(event, data = {}) {
this.$_emitMapEvent(event, { control: this.control, ...data });
},
/**
* Bind events for markers, popups and controls.
* MapboxGL JS emits this events on popup or marker object,
* so we treat them as 'self' events of these objects
*
* @param {object} events - events to bind
* @param {any} emitter - object to bind events to
*/
$_bindSelfEvents(events, emitter) {
Object.keys(this.$listeners).forEach((eventName) => {
if (events.includes(eventName)) {
emitter.on(eventName, this.$_emitSelfEvent);
}
});
},
$_unbindSelfEvents(events, emitter) {
if (events.length === 0) return;
if (!emitter) return;
events.forEach((eventName) => {
emitter.off(eventName, this.$_emitSelfEvent);
});
},
},
});
// import withRegistration from "../../../lib/withRegistration";
var controlMixin = Vue.extend({
mixins: [withEventsMixin, withSelfEventsMixin],
inject: ['mapbox', 'map', 'actions'],
props: {
position: {
type: String,
default: 'top-right',
},
},
watch: {
position() {
this.$_removeControl();
this.$_addControl();
},
},
beforeDestroy() {
this.$_removeControl();
},
methods: {
$_addControl() {
try {
this.map.addControl(this.control, this.position);
} catch (err) {
this.$_emitEvent('error', { error: err });
return;
}
this.$_emitEvent('added', { control: this.control });
},
$_removeControl() {
try {
if (this.map && this.control) {
this.map.removeControl(this.control);
}
} catch (err) {
this.$_emitEvent('error', { error: err });
return;
}
this.$_emitEvent('removed', { control: this.control });
},
},
});
var AttributionControl = Vue.extend({
name: 'AttributionControl',
mixins: [controlMixin],
props: {
compact: {
type: Boolean,
default: true,
},
customAttribution: {
type: [String, Array],
default: undefined,
},
},
created() {
this.control = new this.mapbox.AttributionControl(this.$props);
this.$_addControl();
},
render() {},
});
var FullscreenControl = Vue.extend({
name: 'FullscreenControl',
mixins: [controlMixin],
props: {
container: {
type: HTMLElement,
default: undefined,
},
},
created() {
this.control = new this.mapbox.FullscreenControl(this.$props);
this.$_addControl();
},
render() {},
});
const geolocationEvents = {
trackuserlocationstart: 'trackuserlocationstart',
trackuserlocationend: 'trackuserlocationend',
geolocate: 'geolocate',
error: 'error',
};
var GeolocateControl = Vue.extend({
name: 'GeolocateControl',
mixins: [withEventsMixin, withSelfEventsMixin, controlMixin],
props: {
positionOptions: {
type: Object,
default() {
return {
enableHighAccuracy: false,
timeout: 6000,
};
},
},
fitBoundsOptions: {
type: Object,
default: () => ({ maxZoom: 15 }),
},
trackUserLocation: {
type: Boolean,
default: false,
},
showAccuracyCircle: {
type: Boolean,
default: false,
},
showUserLocation: {
type: Boolean,
default: true,
},
},
created() {
const GeolocateControl = this.mapbox.GeolocateControl;
this.control = new GeolocateControl(this.$props);
this.$_addControl();
this.$_bindSelfEvents(Object.keys(geolocationEvents), this.control);
},
methods: {
trigger() {
if (this.control) {
return this.control.trigger();
}
},
},
render() {},
});
class SlotControl {
constructor(options = {}, element) {
this.options = options;
this.element = element;
}
onAdd() {
this.element.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
return this.element;
}
onRemove() {
this.element.parentNode.removeChild(this.element);
}
}
var IControl = Vue.extend({
name: 'IControl',
mixins: [controlMixin],
mounted() {
this.control = new SlotControl(this.$attrs, this.$el);
this.$_addControl();
},
render(createElement) {
return createElement('div', this.$slots.default);
},
});
var NavigationControl = Vue.extend({
name: 'NavigationControl',
mixins: [controlMixin],
props: {
showCompass: {
type: Boolean,
default: true,
},
showZoom: {
type: Boolean,
default: true,
},
visualizePitch: {
type: Boolean,
default: true,
},
},
created() {
this.control = new this.mapbox.NavigationControl(this.$props);
this.$_addControl();
},
render() {},
});
var ScaleControl = Vue.extend({
name: 'ScaleControl',
mixins: [controlMixin],
props: {
maxWidth: {
type: Number,
default: 150,
},
unit: {
type: String,
default: 'metric',
validator(value) {
return ['imperial', 'metric', 'nautical'].includes(value);
},
},
},
watch: {
unit(next, prev) {
if (this.control && next !== prev) {
this.control.setUnit(next);
}
},
},
created() {
this.control = new this.mapbox.ScaleControl(this.$props);
this.$_addControl();
},
render() {},
});
const markerEvents = {
drag: 'drag',
dragstart: 'dragstart',
dragend: 'dragend',
};
const markerDOMEvents = {
click: 'click',
mouseenter: 'mouseenter',
mouseleave: 'mouseleave',
};
var script$1 = Vue.extend({
name: 'MapMarker',
mixins: [withEventsMixin, withSelfEventsMixin],
inject: ['mapbox', 'map'],
provide() {
const self = this;
return {
get marker() {
return self.marker;
},
};
},
props: {
// mapbox marker options
offset: {
type: [Object, Array],
default: () => [0, -14],
},
coordinates: {
type: Array,
required: true,
},
color: {
type: String,
default: 'blue',
},
anchor: {
type: String,
default: 'center',
},
draggable: {
type: Boolean,
default: false,
},
},
data() {
return {
initial: true,
marker: undefined,
};
},
watch: {
coordinates(lngLat) {
if (this.initial) return;
this.marker.setLngLat(lngLat);
},
draggable(next) {
if (this.initial) return;
this.marker.setDraggable(next);
},
},
mounted() {
const markerOptions = {
...this.$props,
};
if (this.$slots.marker) {
markerOptions.element = this.$slots.marker[0].elm;
}
this.marker = new this.mapbox.Marker(markerOptions);
if (this.$listeners['update:coordinates']) {
this.marker.on('dragend', (event) => {
let newCoordinates;
if (this.coordinates instanceof Array) {
newCoordinates = [
event.target._lngLat.lng,
event.target._lngLat.lat,
];
} else {
newCoordinates = event.target._lngLat;
}
this.$emit('update:coordinates', newCoordinates);
});
}
const eventNames = Object.keys(markerEvents);
this.$_bindSelfEvents(eventNames, this.marker);
this.initial = false;
this.$_addMarker();
},
beforeDestroy() {
if (this.map !== undefined && this.marker !== undefined) {
this.marker.remove();
}
},
methods: {
$_addMarker() {
this.marker.setLngLat(this.coordinates).addTo(this.map);
this.$_bindMarkerDOMEvents();
this.$_emitEvent('added', { marker: this.marker });
},
$_emitSelfEvent(event) {
this.$_emitMapEvent(event, { marker: this.marker });
},
$_bindMarkerDOMEvents() {
Object.keys(this.$listeners).forEach((key) => {
if (Object.values(markerDOMEvents).includes(key)) {
this.marker._element.addEventListener(key, (event) => {
this.$_emitSelfEvent(event);
});
}
});
},
remove() {
this.marker.remove();
this.$_emitEvent('removed');
},
togglePopup() {
return this.marker.togglePopup();
},
},
});
/* script */
const __vue_script__$1 = script$1;
/* template */
var __vue_render__$1 = function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div",
{ staticClass: "mgl-hidden" },
[_vm._t("marker"), _vm._v(" "), _vm.marker ? _vm._t("default") : _vm._e()],
2
)
};
var __vue_staticRenderFns__$1 = [];
__vue_render__$1._withStripped = true;
/* style */
const __vue_inject_styles__$1 = undefined;
/* scoped */
const __vue_scope_id__$1 = undefined;
/* module identifier */
const __vue_module_identifier__$1 = undefined;
/* functional template */
const __vue_is_functional_template__$1 = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__$1 = /*#__PURE__*/normalizeComponent(
{ render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 },
__vue_inject_styles__$1,
__vue_script__$1,
__vue_scope_id__$1,
__vue_is_functional_template__$1,
__vue_module_identifier__$1,
false,
undefined,
undefined,
undefined
);
var Marker = __vue_component__$1;
const popupEvents = {
open: 'open',
close: 'close',
};
/**
* Popup component.
*
* @see See [Mapbox Gl JS Popup](https://www.mapbox.com/mapbox-gl-js/api/#popup)
*/
var script = Vue.extend({
name: 'MglPopup',
mixins: [withEventsMixin, withSelfEventsMixin],
inject: {
mapbox: {
default: null,
},
map: {
default: null,
},
marker: {
default: null,
},
},
props: {
/**
* Mapbox GL popup option.
* Space-separated CSS class names to add to popup container
*/
className: {
type: String,
default: undefined,
},
/**
* If `true`, a close button will appear in the top right corner of the popup.
* Mapbox GL popup option.
*/
closeButton: {
type: Boolean,
default: true,
},
/**
* Mapbox GL popup option.
* If `true`, the popup will closed when the map is clicked. .
*/
closeOnClick: {
type: Boolean,
default: true,
},
/**
* Mapbox GL popup option.
* If `true`, the popup will closed when the map moves.
*/
closeOnMove: {
type: Boolean,
default: false,
},
/**
* Mapbox GL popup option.
* If `true`, the popup will try to focus the first focusable element inside the popup.
*/
focusAfterOpen: {
type: Boolean,
default: true,
},
/**
* Mapbox GL popup option.
* A string indicating the popup's location relative to the coordinate set.
* If unset the anchor will be dynamically set to ensure the popup falls within the map container with a preference for 'bottom' .
* 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'
*/
anchor: {
validator(value) {
let allowedValues = [
'top',
'bottom',
'left',
'right',
'top-left',
'top-right',
'bottom-left',
'bottom-right',
];
return typeof value === 'string' && allowedValues.includes(value);
},
default: undefined,
},
/**
* Mapbox GL popup option.
* A pixel offset applied to the popup's location
* a single number specifying a distance from the popup's location
* a PointLike specifying a constant offset
* an object of Points specifing an offset for each anchor position Negative offsets indicate left and up.
*/
offset: {
type: [Number, Object, Array],
default: () => [0, 0],
},
coordinates: {
type: Array,
default: ()