support tiles in GeoJSON

This commit is contained in:
Maurits van der Schee 2019-07-03 13:43:12 +02:00
commit b08f63320c
2 changed files with 281 additions and 0 deletions

View file

@ -0,0 +1,167 @@
/*
* Leaflet.GeoJSONGridLayer
*/
(function () {
var console = window.console || {
error: function () {},
warn: function () {}
};
function defineLeafletGeoJSONGridLayer(L) {
L.GeoJSONGridLayer = L.GridLayer.extend({
initialize: function (url, options) {
L.GridLayer.prototype.initialize.call(this, options);
this._url = url;
this._geojsons = {};
this._features = {};
this.geoJsonClass = (this.options.geoJsonClass ? this.options.geoJsonClass : L.GeoJSON);
},
onAdd: function (map) {
var layers = this._geojsons;
Object.keys(layers).forEach(function (key) {
map.addLayer(layers[key]);
});
L.GridLayer.prototype.onAdd.call(this, map);
this.zoomanimHandler = this._handleZoom.bind(this);
map.on('zoomanim', this.zoomanimHandler);
},
onRemove: function (map) {
var layers = this._geojsons;
Object.keys(layers).forEach(function (key) {
map.removeLayer(layers[key]);
});
L.GridLayer.prototype.onRemove.call(this, map);
map.off('zoomanim', this.zoomanimHandler);
},
_handleZoom: function (e) {
this.checkZoomConditions(e.zoom);
},
createTile: function (coords, done) {
var tile = L.DomUtil.create('div', 'leaflet-tile');
var size = this.getTileSize();
tile.width = size.x;
tile.height = size.y;
this.fetchTile(coords, function (error) {
done(error, tile);
});
return tile;
},
fetchTile: function (coords, done) {
var tileUrl = L.Util.template(this._url, coords);
var tileLayer = this;
var request = new XMLHttpRequest();
request.open('GET', tileUrl, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
var data = JSON.parse(request.responseText);
tileLayer.addData(data);
done(null);
} else {
// We reached our target server, but it returned an error
done(request.statusText);
}
};
request.onerror = function () {
done(request.statusText);
};
request.send();
},
getLayers: function () {
var geojsons = this._geojsons,
layers = [];
Object.keys(geojsons).forEach(function (key) {
layers.push(geojsons[key]);
});
return layers;
},
hasLayerWithId: function (sublayer, id) {
if (!this._geojsons[sublayer] || !this._features[sublayer]) return false;
return this._features[sublayer].hasOwnProperty(id);
},
addData: function (data) {
if (data.type === 'FeatureCollection') {
this.addSubLayerData('default', data);
}
else {
var tileLayer = this;
Object.keys(data).forEach(function (key) {
tileLayer.addSubLayerData(key, data[key]);
});
}
},
addSubLayerData: function (sublayer, data) {
if (!this._geojsons[sublayer]) {
this._geojsons[sublayer] = new this.geoJsonClass(null, this.options.layers[sublayer]).addTo(this._map);
this.checkZoomConditions(this._map.getZoom());
}
var toAdd = data.features.filter(function (feature) {
return !this.hasLayerWithId(sublayer, feature.id ? feature.id : feature.properties.id);
}, this);
if (!this._features[sublayer]) {
this._features[sublayer] = {};
}
toAdd.forEach(function (feature) {
var id = feature.id ? feature.id : feature.properties.id;
this._features[sublayer][id] = feature;
}, this);
this._geojsons[sublayer].addData({
type: 'FeatureCollection',
features: toAdd
});
},
checkZoomConditions: function (zoom) {
var layers = this._geojsons,
map = this._map;
Object.keys(layers).forEach(function (key) {
var layer = layers[key],
options = layer.options;
if ((options.maxZoom && zoom > options.maxZoom) ||
(options.minZoom && zoom < options.minZoom)) {
map.removeLayer(layer);
}
else {
map.addLayer(layer);
}
});
}
});
L.geoJsonGridLayer = function(url, options) {
return new L.GeoJSONGridLayer(url, options);
};
}
if (typeof define === 'function' && define.amd) {
// Try to add leaflet.loading to Leaflet using AMD
define(['leaflet'], function (L) {
defineLeafletGeoJSONGridLayer(L);
});
}
else {
// Else use the global L
defineLeafletGeoJSONGridLayer(L);
}
})();

View file

@ -0,0 +1,114 @@
/* global L */
(function() {
L.GeoJSONTileLayer = L.GeoJSON.extend({
includes: L.Evented.prototype,
map: null,
options: {
},
initialize(extraOptions, options) {
L.GeoJSON.prototype.initialize.call(this, [], options);
L.Util.setOptions(this, extraOptions);
},
/*
function long2tile(lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); }
function lat2tile(lat,zoom) { return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); }
Inverse process:
function tile2long(x,z) {
return (x/Math.pow(2,z)*360-180);
}
function tile2lat(y,z) {
var n=Math.PI-2*Math.PI*y/Math.pow(2,z);
return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));
}
Example for calculating number of tiles within given extent and zoom-level:
var zoom = 9;
var top_tile = lat2tile(north_edge, zoom); // eg.lat2tile(34.422, 9);
var left_tile = lon2tile(west_edge, zoom);
var bottom_tile = lat2tile(south_edge, zoom);
var right_tile = lon2tile(east_edge, zoom);
var width = Math.abs(left_tile - right_tile) + 1;
var height = Math.abs(top_tile - bottom_tile) + 1;
// total tiles
var total_tiles = width * height; // -> eg. 377
*/
_reload: function() {
if (this.map) {
var urls = this._expand(this.options.url);
for (var i=0; i<urls.length; i++) {
this._ajax('GET', urls[i], false, this._update.bind(this));
}
}
},
_update: function(geoData) {
this.clearLayers();
this.addData(geoData);
},
onAdd: function(map) {
L.GeoJSON.prototype.onAdd.call(this, map);
this.map = map;
map.on('moveend zoomend refresh', this._reload, this);
this._reload();
},
onRemove: function(map) {
map.off('moveend zoomend refresh', this._reload, this);
this.map = null;
L.GeoJSON.prototype.onRemove.call(this, map);
},
_expand: function(template) {
var bbox = this._map.getBounds();
var southWest = bbox.getSouthWest();
var northEast = bbox.getNorthEast();
var bboxStr = bbox.toBBoxString();
var coords = {
lat1: southWest.lat,
lon1: southWest.lng,
lat2: northEast.lat,
lon2: northEast.lng,
bbox: bboxStr
};
return [L.Util.template(template, coords)];
},
_ajax: function(method, url, data, callback) {
var request = new XMLHttpRequest();
request.open(method, url, true);
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200) {
callback(JSON.parse(request.responseText));
}
};
if (data) {
request.setRequestHeader('Content-type', 'application/json');
request.send(JSON.stringify(data));
} else {
request.send();
}
return request;
},
});
L.geoJSONTileLayer = function (options) {
return new L.GeoJSONTileLayer(options);
};
}).call(this);