Skip to content

Commit b08f633

Browse files
committed
support tiles in GeoJSON
1 parent 8daaf21 commit b08f633

File tree

2 files changed

+281
-0
lines changed

2 files changed

+281
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Leaflet.GeoJSONGridLayer
3+
*/
4+
5+
(function () {
6+
7+
var console = window.console || {
8+
error: function () {},
9+
warn: function () {}
10+
};
11+
12+
function defineLeafletGeoJSONGridLayer(L) {
13+
L.GeoJSONGridLayer = L.GridLayer.extend({
14+
initialize: function (url, options) {
15+
L.GridLayer.prototype.initialize.call(this, options);
16+
17+
this._url = url;
18+
this._geojsons = {};
19+
this._features = {};
20+
this.geoJsonClass = (this.options.geoJsonClass ? this.options.geoJsonClass : L.GeoJSON);
21+
},
22+
23+
onAdd: function (map) {
24+
var layers = this._geojsons;
25+
Object.keys(layers).forEach(function (key) {
26+
map.addLayer(layers[key]);
27+
});
28+
29+
L.GridLayer.prototype.onAdd.call(this, map);
30+
this.zoomanimHandler = this._handleZoom.bind(this);
31+
map.on('zoomanim', this.zoomanimHandler);
32+
},
33+
34+
onRemove: function (map) {
35+
var layers = this._geojsons;
36+
Object.keys(layers).forEach(function (key) {
37+
map.removeLayer(layers[key]);
38+
});
39+
40+
L.GridLayer.prototype.onRemove.call(this, map);
41+
map.off('zoomanim', this.zoomanimHandler);
42+
},
43+
44+
_handleZoom: function (e) {
45+
this.checkZoomConditions(e.zoom);
46+
},
47+
48+
createTile: function (coords, done) {
49+
var tile = L.DomUtil.create('div', 'leaflet-tile');
50+
var size = this.getTileSize();
51+
tile.width = size.x;
52+
tile.height = size.y;
53+
54+
this.fetchTile(coords, function (error) {
55+
done(error, tile);
56+
});
57+
return tile;
58+
},
59+
60+
fetchTile: function (coords, done) {
61+
var tileUrl = L.Util.template(this._url, coords);
62+
var tileLayer = this;
63+
64+
var request = new XMLHttpRequest();
65+
request.open('GET', tileUrl, true);
66+
67+
request.onload = function () {
68+
if (request.status >= 200 && request.status < 400) {
69+
var data = JSON.parse(request.responseText);
70+
tileLayer.addData(data);
71+
done(null);
72+
} else {
73+
// We reached our target server, but it returned an error
74+
done(request.statusText);
75+
}
76+
};
77+
78+
request.onerror = function () {
79+
done(request.statusText);
80+
};
81+
82+
request.send();
83+
},
84+
85+
getLayers: function () {
86+
var geojsons = this._geojsons,
87+
layers = [];
88+
Object.keys(geojsons).forEach(function (key) {
89+
layers.push(geojsons[key]);
90+
});
91+
return layers;
92+
},
93+
94+
hasLayerWithId: function (sublayer, id) {
95+
if (!this._geojsons[sublayer] || !this._features[sublayer]) return false;
96+
return this._features[sublayer].hasOwnProperty(id);
97+
},
98+
99+
addData: function (data) {
100+
if (data.type === 'FeatureCollection') {
101+
this.addSubLayerData('default', data);
102+
}
103+
else {
104+
var tileLayer = this;
105+
Object.keys(data).forEach(function (key) {
106+
tileLayer.addSubLayerData(key, data[key]);
107+
});
108+
}
109+
},
110+
111+
addSubLayerData: function (sublayer, data) {
112+
if (!this._geojsons[sublayer]) {
113+
this._geojsons[sublayer] = new this.geoJsonClass(null, this.options.layers[sublayer]).addTo(this._map);
114+
this.checkZoomConditions(this._map.getZoom());
115+
}
116+
var toAdd = data.features.filter(function (feature) {
117+
return !this.hasLayerWithId(sublayer, feature.id ? feature.id : feature.properties.id);
118+
}, this);
119+
120+
if (!this._features[sublayer]) {
121+
this._features[sublayer] = {};
122+
}
123+
toAdd.forEach(function (feature) {
124+
var id = feature.id ? feature.id : feature.properties.id;
125+
this._features[sublayer][id] = feature;
126+
}, this);
127+
128+
this._geojsons[sublayer].addData({
129+
type: 'FeatureCollection',
130+
features: toAdd
131+
});
132+
},
133+
134+
checkZoomConditions: function (zoom) {
135+
var layers = this._geojsons,
136+
map = this._map;
137+
Object.keys(layers).forEach(function (key) {
138+
var layer = layers[key],
139+
options = layer.options;
140+
if ((options.maxZoom && zoom > options.maxZoom) ||
141+
(options.minZoom && zoom < options.minZoom)) {
142+
map.removeLayer(layer);
143+
}
144+
else {
145+
map.addLayer(layer);
146+
}
147+
});
148+
}
149+
});
150+
151+
L.geoJsonGridLayer = function(url, options) {
152+
return new L.GeoJSONGridLayer(url, options);
153+
};
154+
}
155+
156+
if (typeof define === 'function' && define.amd) {
157+
// Try to add leaflet.loading to Leaflet using AMD
158+
define(['leaflet'], function (L) {
159+
defineLeafletGeoJSONGridLayer(L);
160+
});
161+
}
162+
else {
163+
// Else use the global L
164+
defineLeafletGeoJSONGridLayer(L);
165+
}
166+
167+
})();
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/* global L */
2+
(function() {
3+
4+
L.GeoJSONTileLayer = L.GeoJSON.extend({
5+
6+
includes: L.Evented.prototype,
7+
8+
map: null,
9+
10+
options: {
11+
},
12+
13+
initialize(extraOptions, options) {
14+
L.GeoJSON.prototype.initialize.call(this, [], options);
15+
L.Util.setOptions(this, extraOptions);
16+
},
17+
18+
19+
/*
20+
function long2tile(lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); }
21+
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))); }
22+
23+
Inverse process:
24+
25+
function tile2long(x,z) {
26+
return (x/Math.pow(2,z)*360-180);
27+
}
28+
function tile2lat(y,z) {
29+
var n=Math.PI-2*Math.PI*y/Math.pow(2,z);
30+
return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));
31+
}
32+
33+
Example for calculating number of tiles within given extent and zoom-level:
34+
35+
var zoom = 9;
36+
var top_tile = lat2tile(north_edge, zoom); // eg.lat2tile(34.422, 9);
37+
var left_tile = lon2tile(west_edge, zoom);
38+
var bottom_tile = lat2tile(south_edge, zoom);
39+
var right_tile = lon2tile(east_edge, zoom);
40+
var width = Math.abs(left_tile - right_tile) + 1;
41+
var height = Math.abs(top_tile - bottom_tile) + 1;
42+
43+
// total tiles
44+
var total_tiles = width * height; // -> eg. 377
45+
*/
46+
47+
_reload: function() {
48+
if (this.map) {
49+
var urls = this._expand(this.options.url);
50+
for (var i=0; i<urls.length; i++) {
51+
this._ajax('GET', urls[i], false, this._update.bind(this));
52+
}
53+
}
54+
},
55+
56+
57+
58+
_update: function(geoData) {
59+
this.clearLayers();
60+
this.addData(geoData);
61+
},
62+
63+
onAdd: function(map) {
64+
L.GeoJSON.prototype.onAdd.call(this, map);
65+
this.map = map;
66+
map.on('moveend zoomend refresh', this._reload, this);
67+
this._reload();
68+
},
69+
70+
onRemove: function(map) {
71+
map.off('moveend zoomend refresh', this._reload, this);
72+
this.map = null;
73+
L.GeoJSON.prototype.onRemove.call(this, map);
74+
},
75+
76+
_expand: function(template) {
77+
var bbox = this._map.getBounds();
78+
var southWest = bbox.getSouthWest();
79+
var northEast = bbox.getNorthEast();
80+
var bboxStr = bbox.toBBoxString();
81+
var coords = {
82+
lat1: southWest.lat,
83+
lon1: southWest.lng,
84+
lat2: northEast.lat,
85+
lon2: northEast.lng,
86+
bbox: bboxStr
87+
};
88+
return [L.Util.template(template, coords)];
89+
},
90+
91+
_ajax: function(method, url, data, callback) {
92+
var request = new XMLHttpRequest();
93+
request.open(method, url, true);
94+
request.onreadystatechange = function() {
95+
if (request.readyState === 4 && request.status === 200) {
96+
callback(JSON.parse(request.responseText));
97+
}
98+
};
99+
if (data) {
100+
request.setRequestHeader('Content-type', 'application/json');
101+
request.send(JSON.stringify(data));
102+
} else {
103+
request.send();
104+
}
105+
return request;
106+
},
107+
108+
});
109+
110+
L.geoJSONTileLayer = function (options) {
111+
return new L.GeoJSONTileLayer(options);
112+
};
113+
114+
}).call(this);

0 commit comments

Comments
 (0)