mapbox-gl
Version:
A WebGL interactive maps library
230 lines (189 loc) • 6.81 kB
JavaScript
'use strict';
var util = require('../util/util');
var Evented = require('../util/evented');
var TilePyramid = require('./tile_pyramid');
var Source = require('./source');
var urlResolve = require('resolve-url');
var EXTENT = require('../data/buffer').EXTENT;
module.exports = GeoJSONSource;
/**
* Create a GeoJSON data source instance given an options object
* @class GeoJSONSource
* @param {Object} [options]
* @param {Object|string} options.data A GeoJSON data object or URL to it. The latter is preferable in case of large GeoJSON files.
* @param {number} [options.maxzoom=14] Maximum zoom to preserve detail at.
* @param {number} [options.buffer] Tile buffer on each side in pixels.
* @param {number} [options.tolerance] Simplification tolerance (higher means simpler) in pixels.
* @param {number} [options.cluster] If the data is a collection of point features, setting this to true clusters the points by radius into groups.
* @param {number} [options.clusterRadius=50] Radius of each cluster when clustering points, in pixels.
* @param {number} [options.clusterMaxZoom] Max zoom to cluster points on. Defaults to one zoom less than `maxzoom` (so that last zoom features are not clustered).
* @example
* var sourceObj = new mapboxgl.GeoJSONSource({
* data: {
* "type": "FeatureCollection",
* "features": [{
* "type": "Feature",
* "geometry": {
* "type": "Point",
* "coordinates": [
* -76.53063297271729,
* 39.18174077994108
* ]
* }
* }]
* }
* });
* map.addSource('some id', sourceObj); // add
* map.removeSource('some id'); // remove
*/
function GeoJSONSource(options) {
options = options || {};
this._data = options.data;
if (options.maxzoom !== undefined) this.maxzoom = options.maxzoom;
var scale = EXTENT / this.tileSize;
this.geojsonVtOptions = {
buffer: (options.buffer !== undefined ? options.buffer : 128) * scale,
tolerance: (options.tolerance !== undefined ? options.tolerance : 0.375) * scale,
extent: EXTENT,
maxZoom: this.maxzoom
};
this.cluster = options.cluster || false;
this.superclusterOptions = {
maxZoom: Math.max(options.clusterMaxZoom, this.maxzoom - 1) || (this.maxzoom - 1),
extent: EXTENT,
radius: (options.clusterRadius || 50) * scale,
log: false
};
this._pyramid = new TilePyramid({
tileSize: this.tileSize,
minzoom: this.minzoom,
maxzoom: this.maxzoom,
reparseOverscaled: true,
load: this._loadTile.bind(this),
abort: this._abortTile.bind(this),
unload: this._unloadTile.bind(this),
add: this._addTile.bind(this),
remove: this._removeTile.bind(this),
redoPlacement: this._redoTilePlacement.bind(this)
});
}
GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototype */{
minzoom: 0,
maxzoom: 14,
tileSize: 512,
_dirty: true,
isTileClipped: true,
/**
* Update source geojson data and rerender map
*
* @param {Object|string} data A GeoJSON data object or URL to it. The latter is preferable in case of large GeoJSON files.
* @returns {GeoJSONSource} this
*/
setData: function(data) {
this._data = data;
this._dirty = true;
this.fire('change');
if (this.map)
this.update(this.map.transform);
return this;
},
onAdd: function(map) {
this.map = map;
},
loaded: function() {
return this._loaded && this._pyramid.loaded();
},
update: function(transform) {
if (this._dirty) {
this._updateData();
}
if (this._loaded) {
this._pyramid.update(this.used, transform);
}
},
reload: function() {
if (this._loaded) {
this._pyramid.reload();
}
},
serialize: function() {
return {
type: 'geojson',
data: this._data
};
},
getVisibleCoordinates: Source._getVisibleCoordinates,
getTile: Source._getTile,
featuresAt: Source._vectorFeaturesAt,
featuresIn: Source._vectorFeaturesIn,
_updateData: function() {
this._dirty = false;
var data = this._data;
if (typeof data === 'string' && typeof window != 'undefined') {
data = urlResolve(window.location.href, data);
}
this.workerID = this.dispatcher.send('parse geojson', {
data: data,
tileSize: this.tileSize,
source: this.id,
geojsonVtOptions: this.geojsonVtOptions,
cluster: this.cluster,
superclusterOptions: this.superclusterOptions
}, function(err) {
this._loaded = true;
if (err) {
this.fire('error', {error: err});
} else {
this._pyramid.reload();
this.fire('change');
}
}.bind(this));
},
_loadTile: function(tile) {
var overscaling = tile.coord.z > this.maxzoom ? Math.pow(2, tile.coord.z - this.maxzoom) : 1;
var params = {
uid: tile.uid,
coord: tile.coord,
zoom: tile.coord.z,
maxZoom: this.maxzoom,
tileSize: this.tileSize,
source: this.id,
overscaling: overscaling,
angle: this.map.transform.angle,
pitch: this.map.transform.pitch,
collisionDebug: this.map.collisionDebug
};
tile.workerID = this.dispatcher.send('load geojson tile', params, function(err, data) {
tile.unloadVectorData(this.map.painter);
if (tile.aborted)
return;
if (err) {
this.fire('tile.error', {tile: tile});
return;
}
tile.loadVectorData(data);
if (tile.redoWhenDone) {
tile.redoWhenDone = false;
tile.redoPlacement(this);
}
this.fire('tile.load', {tile: tile});
}.bind(this), this.workerID);
},
_abortTile: function(tile) {
tile.aborted = true;
},
_addTile: function(tile) {
this.fire('tile.add', {tile: tile});
},
_removeTile: function(tile) {
this.fire('tile.remove', {tile: tile});
},
_unloadTile: function(tile) {
tile.unloadVectorData(this.map.painter);
this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, null, tile.workerID);
},
redoPlacement: Source.redoPlacement,
_redoTilePlacement: function(tile) {
tile.redoPlacement(this);
}
});