Skip to content

Commit

Permalink
Rework for Leaflet 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
perliedman committed May 31, 2017
1 parent be7314a commit 0bfc0ac
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 53 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Leaflet Underneath
[![NPM version](https://img.shields.io/npm/v/leaflet-underneath.svg)](https://www.npmjs.com/package/leaflet-underneath) !

[![NPM version](https://img.shields.io/npm/v/leaflet-underneath.svg)](https://www.npmjs.com/package/leaflet-underneath) ![Leaflet 1.0 compatible!](https://img.shields.io/badge/Leaflet%201.0-%E2%9C%93-1EB300.svg?style=flat)

[Check out the demo](http://www.liedman.net/leaflet-underneath/)

With a normal tile layer, the user can't interact to find out more about a location, since it is a static image. With this plugin, you can find out what features are underneath the current mouse position, for example when the user clicks the map.

This is done using [Mapbox Vector Tiles](https://www.mapbox.com/developers/vector-tiles/), that are queried for features in a way that is both fast and reasonably bandwidth efficient.

### Leaflet 1.0 compatibility

Version 2.0 and up of Leaflet Underneath is only compatible with Leaflet 1.0; earlier versions only work with Leaflet 0.7.

## Using

[Download](https://github.com/perliedman/leaflet-underneath/releases) the code. Include the pre-built Leaflet Underneath script in your project:
Expand All @@ -25,34 +30,33 @@ npm install --save leaflet-underneath
var L = require('leaflet');
require('leaflet-underneath');

// Leaflet Underneath will be available as L.tileLayer.underneath
// Leaflet Underneath will be available as L.underneath
```

For a complete example on how to use Leaflet Underneath, see basic [Leaflet Underneath example](https://github.com/perliedman/leaflet-underneath/blob/master/example/index.js).

## API

### L.TileLayer.Underneath
### L.Underneath

Leaflet Underneath uses a tilelayer that can be queried for features from a location. The layer extends
[L.TileLayer](http://leafletjs.com/reference.html#tilelayer).
Leaflet Underneath can be queried for features from a location.

#### Creation

Factory | Description
---------------------|-----------------------------
`L.tileLayer.underneath(<String> tileUrl, <`[`UnderneathOptions`](#underneathoptions)`> options?) | Instantiates a new Leaflet Underneath layer
`L.underneath(<String> tileUrl, <Map> map, <`[`UnderneathOptions`](#underneathoptions)`> options?) | Instantiates a new Leaflet Underneath layer

#### Options

In addition to Leaflet's built-in [TileLayer options](http://leafletjs.com/reference.html#tilelayer-options), Leaflet Underneath has these options:

Option | Type | Default | Description
-----------------------|---------------|----------------------|----------------------------
`minZoom` | `Number` | `0` | Minimum zoom level in the tile set
`maxZoom` | `Number` | `22` | Maximum zoom level in the tile set
`subdomains` | `Array` | `['a', 'b', 'c']` | Available subdomains
`layers` | `String[]` | `[]` | Names of layers to include in search
`defaultRadius` | `Number` | `20` | Default number of pixels search radius
`featureId` | `Function` | | Function that returns a unique feature id; used to filter out duplicates. Default returns a features `osm_id`property
`lazy` | `Boolean` | `true` | If lazy, only tiles that are queried will be loaded. Otherwise, all tiles will be loaded, like a normal tile layer.
`zoomIn` | `Number` | `0` | Zoom in relative to the map's current zoom level when making a query; used to get more or less detailed results than current zoom would give
`joinFeatures` | `Boolean` | `false` | For features with same id, should geometries be joined (`true`), or should they be ignored (`false`)

Expand Down
4 changes: 2 additions & 2 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<title></title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@0.7.7/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
<link rel="stylesheet" type="text/css" href="https://www.mapbox.com/maki/www/maki-sprite.css" />
<style type="text/css">
body {
Expand Down Expand Up @@ -34,7 +34,7 @@
<body>
<div id="map"></div>

<script src="https://unpkg.com/leaflet@0.7.7/dist/leaflet-src.js"></script>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet-src.js"></script>
<script src="../dist/leaflet-underneath-src.js"></script>
<script src="index.js"></script>
</body>
Expand Down
8 changes: 3 additions & 5 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

var pois = L.tileLayer.underneath('http://{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
'{z}/{x}/{y}.vector.pbf?access_token=<your-token-here>', {
var pois = L.underneath('http://{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
'{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoibGllZG1hbiIsImEiOiJjaWtjYjh5cGcwMDNhdm5sdmoycmgzY3drIn0.CZCSz1N53qWOVB0j2A_5pg', map, {
layers: ['poi_label'],
lazy: true,
zoomIn: 2
})
.addTo(map);
});

map.on('click', function(e) {
var results = [],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"corslite": "0.0.7",
"leaflet": "^0.7.7",
"leaflet": "^1.0.3",
"pbf": "^1.3.5",
"rbush": "^1.4.2",
"turf-extent": "^1.0.4",
Expand Down
113 changes: 77 additions & 36 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,30 @@ var Protobuf = require('pbf'),
polygon = require('turf-polygon'),
point = require('turf-point');

module.exports = L.TileLayer.Underneath = L.TileLayer.extend({
module.exports = L.Underneath = L.Evented.extend({
options: {
layers: [],
defaultRadius: 20,
featureId: function(f) {
return f.properties.osm_id;
},
lazy: true,
zoomIn: 0,
joinFeatures: false
joinFeatures: false,
tileSize: 256,
minZoom: 0,
maxZoom: 22,
subdomains: ['a', 'b', 'c']
},

initialize: function(tileUrl, options) {
L.TileLayer.prototype.initialize.call(this, tileUrl, options);
initialize: function(tileUrl, map, options) {
L.setOptions(this, options);
this._url = tileUrl;
this._map = map;
this._bush = rbush(this.options.rbushMaxEntries);
this._tiles = {};

map.on('zoomend', this._reset, this);
this._reset();
},

query: function(latLng, cb, context, options) {
Expand All @@ -38,17 +47,12 @@ module.exports = L.TileLayer.Underneath = L.TileLayer.extend({

var p = this._map.project(latLng, z);

if (this.options.lazy) {
this._loadTiles(p, radius, L.bind(function(err) {
if (err) {
return cb(err);
}
this._query(latLng, p, options, cb, context);
}, this));
return this;
}

this._query(p, radius, cb, context);
this._loadTiles(p, radius, L.bind(function(err) {
if (err) {
return cb(err);
}
this._query(latLng, p, options, cb, context);
}, this));
return this;
},

Expand Down Expand Up @@ -89,23 +93,60 @@ module.exports = L.TileLayer.Underneath = L.TileLayer.extend({
_loadTiles: function(p, radius, cb) {
var se = p.add([radius, radius]),
nw = p.subtract([radius, radius]),
tileBounds = L.bounds(
bounds = L.bounds(
nw.divideBy(this.options.tileSize)._floor(),
se.divideBy(this.options.tileSize)._floor());
se.divideBy(this.options.tileSize)._floor()),
queue = [],
center = this._map.unproject(p);

this._forceLoadTiles = true;
this._addTilesFromCenterOut(tileBounds);
this._forceLoadTiles = false;
var j, i, point;

if (this._tilesToLoad) {
this.once('load', function() { cb(); });
} else {
cb();
for (j = bounds.min.y; j <= bounds.max.y; j++) {
for (i = bounds.min.x; i <= bounds.max.x; i++) {
point = new L.Point(i, j);

if (this._tileShouldBeLoaded(point)) {
queue.push(point);
}
}
}

var tilesToLoad = queue.length,
waitingTiles = tilesToLoad;

if (tilesToLoad === 0) { return cb(); }

// load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(center) - b.distanceTo(center);
});

for (i = 0; i < tilesToLoad; i++) {
this._addTile(queue[i], function () {
waitingTiles--;
if (waitingTiles <= 0) {
cb();
}
});
}
},

getTileUrl: function (tilePoint) {
return L.Util.template(this._url, L.extend({
s: this._getSubdomain(tilePoint),
z: tilePoint.z,
x: tilePoint.x,
y: tilePoint.y
}, this.options));
},

_getSubdomain: function (tilePoint) {
var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
return this.options.subdomains[index];
},

_getZoomForUrl: function() {
return L.TileLayer.prototype._getZoomForUrl.call(this) + this.options.zoomIn;
_tileShouldBeLoaded: function(tilePoint) {
return true;
},

_getWrapTileNum: function () {
Expand All @@ -114,15 +155,14 @@ module.exports = L.TileLayer.Underneath = L.TileLayer.extend({
return size.divideBy(this._getTileSize())._floor();
},

_addTile: function(tilePoint, fragment, cb) {
_addTile: function(tilePoint, cb) {
var key = this._tileKey(tilePoint),
tile = { datum: null, processed: false };

if (!this._tiles[key] && (!this.options.lazy || this._forceLoadTiles)) {
if (!this._tiles[key]) {
this._tiles[key] = tile;
return this._loadTile(tile, tilePoint, cb);
} else {
this._tileLoaded();
return cb && cb();
}
},
Expand All @@ -135,7 +175,9 @@ module.exports = L.TileLayer.Underneath = L.TileLayer.extend({
var url,
request;

this._adjustTilePoint(tilePoint);
//this._adjustTilePoint(tilePoint);
tilePoint.z = Math.min(this.options.maxZoom,
Math.max(this.options.minZoom, this._map.getZoom() + this.options.zoomIn));
url = this.getTileUrl(tilePoint);
request = corslite(url, L.bind(function(err, data) {
if (err) {
Expand All @@ -144,19 +186,18 @@ module.exports = L.TileLayer.Underneath = L.TileLayer.extend({
url: url,
error: err
});
this._tileLoaded();
//this._tileLoaded();
return cb && cb(err);
}

this._parseTile(tile, tilePoint, new Uint8Array(data.response));
this._tileLoaded();
//this._tileLoaded();
return cb && cb();
}, this), true);
request.responseType = 'arraybuffer';
},

_reset: function() {
L.TileLayer.prototype._reset.call(this);
this._features = {};
this._bush.clear();
this.fire('featurescleared');
Expand Down Expand Up @@ -264,6 +305,6 @@ module.exports = L.TileLayer.Underneath = L.TileLayer.extend({
}
});

L.tileLayer.underneath = function(tileUrl, options) {
return new L.TileLayer.Underneath(tileUrl, options);
L.underneath = function(tileUrl, map, options) {
return new L.Underneath(tileUrl, map, options);
};

0 comments on commit 0bfc0ac

Please sign in to comment.