├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── bower.json
├── dist
├── Leaflet.MapboxVectorTile.js
└── Leaflet.MapboxVectorTile.min.js
├── docs
└── configuration.md
├── examples
├── basic.html
├── basic.js
├── confetti.html
├── confetti.js
├── dynamic-label.html
├── gadm.html
├── gadm.js
├── india-aggregations.html
├── india-aggregations.js
├── india-label.html
├── india-label.js
├── mapbox-source.html
├── mapbox-source.js
├── osm_seattle.html
├── osm_seattle.js
├── static-label.html
├── static-label.js
├── visibleLayers.html
└── visibleLayers.js
├── lib
├── Leaflet
│ ├── leaflet-0.7.3
│ │ ├── images
│ │ │ ├── layers-2x.png
│ │ │ ├── layers.png
│ │ │ ├── marker-icon-2x.png
│ │ │ ├── marker-icon.png
│ │ │ └── marker-shadow.png
│ │ ├── leaflet-src.js
│ │ ├── leaflet.css
│ │ └── leaflet.js
│ └── leaflet-master
│ │ ├── images
│ │ ├── layers-2x.png
│ │ ├── layers.png
│ │ ├── marker-icon-2x.png
│ │ ├── marker-icon.png
│ │ └── marker-shadow.png
│ │ ├── leaflet-src.js
│ │ ├── leaflet.css
│ │ └── leaflet.js
├── jQuery
│ └── jQuery-2.1.1.min.js
└── jsts
│ ├── javascript.util.js
│ ├── jsts-src.js
│ └── jsts.js
├── package.json
├── src
├── DynamicLabel
│ ├── DynamicLabel.js
│ ├── Feature.js
│ ├── PositionWorker.js
│ └── label.css
├── MVTFeature.js
├── MVTLayer.js
├── MVTSource.js
├── MVTUtil.js
├── StaticLabel
│ ├── StaticLabel.js
│ └── label.css
└── index.js
└── test
├── fixtures
├── basicStaticLabel.js
├── confetti_datasource.js
├── indiaAggregationsMutex.js
├── indiaStaticLabel.js
└── pbfs
│ ├── 13.5889.3494.pbf
│ └── 14.11875.6922.pbf
├── js
├── MVTSource.test.js
└── MVTSource_confetti.test.js
└── visibletests
├── confetti_test.html
└── confetti_test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | ############## generic files to ignore #######
2 | *~
3 | *.lock
4 | *.DS_Store
5 | *.swp
6 | *.out
7 |
8 | ############## Mac Stuff #####################
9 | .DS_Store
10 | .DS_Store?
11 | ._*
12 | .Spotlight-V100
13 | .Trashes
14 | ehthumbs.db
15 | Thumbs.db
16 |
17 | ############## Jetbrains Stuff ###############
18 | *.iws
19 | workspace.xml
20 | tasks.xml
21 | *.iml
22 | .idea/
23 |
24 | ############## NodeJS Stuff ##################
25 |
26 | # Logs
27 | logs
28 | *.log
29 |
30 | # Runtime data
31 | pids
32 | *.pid
33 | *.seed
34 |
35 | # Directory for instrumented libs generated by jscoverage/JSCover
36 | lib-cov
37 |
38 | # Coverage directory used by tools like istanbul
39 | coverage
40 |
41 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
42 | .grunt
43 |
44 | # Compiled binary addons (http://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | node_modules
48 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | ############## generic files to ignore #######
2 | *~
3 | *.lock
4 | *.DS_Store
5 | *.swp
6 | *.out
7 |
8 | ############## Mac Stuff #####################
9 | .DS_Store
10 | .DS_Store?
11 | ._*
12 | .Spotlight-V100
13 | .Trashes
14 | ehthumbs.db
15 | Thumbs.db
16 |
17 | ############## Jetbrains Stuff ###############
18 | *.iws
19 | workspace.xml
20 | tasks.xml
21 | *.iml
22 | .idea/
23 |
24 | ############## NodeJS Stuff ##################
25 |
26 | # Logs
27 | logs
28 | *.log
29 |
30 | # Runtime data
31 | pids
32 | *.pid
33 | *.seed
34 |
35 | # Directory for instrumented libs generated by jscoverage/JSCover
36 | lib-cov
37 |
38 | # Coverage directory used by tools like istanbul
39 | coverage
40 |
41 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
42 | .grunt
43 |
44 | # Compiled binary addons (http://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | node_modules
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Spatial Development International
2 | All rights reserved.
3 |
4 | Source code can be found at:
5 | https://github.com/SpatialServer/Leaflet.MapboxVectorTile
6 |
7 | Permission to use, copy, modify, and/or distribute this software for any
8 | purpose with or without fee is hereby granted, provided that the above
9 | copyright notice and this permission notice appear in all copies.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 | PERFORMANCE OF THIS SOFTWARE.
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Leaflet.MapboxVectorTile
2 | ========================
3 |
4 | [
5 | ](https://ci.testling.com/spatialserver/Leaflet.MapboxVectorTile)
6 |
7 | A Leaflet Plugin that renders Mapbox Vector Tiles on HTML5 Canvas.
8 |
9 | Though there is extensive use of MapboxVectorTiles in Mapnik PNG tile rendering as well as MapboxGL, there is a strange lacking of libraries that integrate these vector tiles directly into Leaflet. Search no more, the library you have been looking for is here!
10 |
11 | Take a look at this [short presentation](https://speakerdeck.com/hallahan/leaflet-vector-tiles) outlining what MapboxVectorTiles are and how this is integrated in Leaflet.
12 |
13 |
14 | ##Changelog
15 |
16 | ### v 0.1.5 - May 21, 2015
17 | * Added point onClick events
18 |
19 |
20 | ### v 0.1.6 - May 26, 2015
21 | * Click function uses style.radius or radius function to do hittest.
22 |
23 | ### v 0.1.7 - December 17, 2015
24 | * Add XHR Headers to be configured for tile requests, update dependencies, correct package.json "main" property.
25 |
26 | ## Examples
27 |
28 | [Basic Usage](http://spatialserver.github.io/Leaflet.MapboxVectorTile/examples/basic.html)
29 |
30 | [Statically Placed Labels](http://spatialserver.github.io/Leaflet.MapboxVectorTile/examples/static-label.html)
31 |
32 | [Confetti (700k Points)](http://spatialserver.github.io/Leaflet.MapboxVectorTile/examples/confetti.html)
33 |
34 | [Fancy Labels Showing Point Aggregations](http://spatialserver.github.io/Leaflet.MapboxVectorTile/examples/india-aggregations.html)
35 |
36 | ## Demo Web Application
37 |
38 | This library is currently being used in the [India Edition of Financial Services for the Poor](http://fspmaps.com/india).
39 |
40 | Tiles are being served by [SpatialServer (PGRestAPI)](https://github.com/spatialdev/PGRestAPI).
41 |
42 | ## Getting Started
43 |
44 | Install the dependencies:
45 |
46 | ```sh
47 | npm install
48 | ```
49 |
50 | Dynamically compile and serve:
51 |
52 | ```sh
53 | npm start
54 | ```
55 |
56 | This puts a watcher on your source directory and recompiles with browserify automatically. It also serves the project directory on port 3000.
57 |
58 | Open your browser to `http://localhost:3000/examples` and see the plugin in action!
59 |
60 | ## Usage
61 |
62 | To put Leaflet.MapboxVectorTile on your map, you create an `L.TileLayer.MVTSource` object. This is a subclass of L.TileLayer, and it's usage and methods work the same as other Leaflet Tile Layers.
63 |
64 | The primary way that you setup your vector tiles is through a configuration object that you send as a parameter to the constructor.
65 |
66 | ```js
67 | var config = {
68 | ...
69 | };
70 |
71 | var mvtSource = new L.TileLayer.MVTSource(config);
72 | map.addLayer(mvtSource);
73 | ```
74 |
75 | Of course if you would like to remove the vector tiles from the map, just call:
76 |
77 | ```js
78 | map.removeLayer(mvtSource);
79 | ```
80 |
81 | Take a look at the [Configuration Documentation](docs/configuration.md) to learn about all of the options you can use and what they mean.
82 |
83 | ## Testing
84 |
85 | We are using [Tape](https://www.npmjs.org/package/tape), [Faucet](https://github.com/substack/faucet), and [Testling](https://ci.testling.com/) to do tests.
86 |
87 | Upon push, tests are automatically run. You can see the [Testling CI Test Results](https://ci.testling.com/spatialserver/Leaflet.MapboxVectorTile) for the latest commit.
88 |
89 | If you want to see if tests pass (on your local system's browser), run:
90 |
91 | ```js
92 | npm test
93 | ```
94 |
95 | Unit tests are in `test/js/`. All tests are written with Tape.
96 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Leaflet.MapboxVectorTile",
3 | "version": "0.1.7",
4 | "main": "dist/Leaflet.MapboxVectorTile.js",
5 | "ignore": [
6 | "docs",
7 | "examples",
8 | "lib",
9 | "src",
10 | "test",
11 | ".gitignore",
12 | ".npmgignore",
13 | "README.md",
14 | "package.json",
15 | "bower.json"
16 | ],
17 | "dependencies": {
18 | "leaflet": "~0.7.3"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration Documentation
2 |
3 | Take a look at the javascript files in the [examples](../examples). You can see a number of working configurations there.
4 |
5 | The following are properties that define a config object for the `MVTSource` object used as follows:
6 |
7 | ```js
8 | var config = {
9 | ...
10 | };
11 |
12 | var mvtSource = new L.TileLayer.MVTSource(config);
13 | map.addLayer(mvtSource);
14 | ```
15 |
16 | ## Config
17 |
18 | * `url` - **{string}** The URL Endpoint that we fetch MVT Protocal Buffer tiles from. **Required**.
19 |
20 | ```js
21 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gaul_fsp_india/{z}/{x}/{y}.pbf",
22 | ```
23 |
24 | * `debug` - **{boolean}** Flagging debug as true provides a grid that shows the edge of the tiles and the z,x,y coordinate address of the tiles. **Default: `false`***.
25 |
26 | ```js
27 | debug: true,
28 | ```
29 |
30 | * `clickableLayers` - **[{string}, ...]** A list of vector tile layers that will capture mouse click events and be selectable on the map. **Default: `null`**.
31 |
32 | ```js
33 | clickableLayers: ['gaul_2014_adm1'],
34 | ```
35 |
36 | * `mutexToggle` - **{boolean}** If this is set to `true`, only one feature can be selected at a time. If it is `false`, multiple features can be selected simultaneously. Clicking again on a selected feature deselects it. **Default: `false`***.
37 |
38 | ```js
39 | mutexToggle: true,
40 | ```
41 |
42 | * `getIDForLayerFeature` - **{function}** Each MVT Feature needs a unique ID. You can specify a specific function to create a unique ID that will be associated with a given feature. *TODO: We need a default function that properly fetches the _id property by deafult.* **Required**.
43 |
44 | ```js
45 | getIDForLayerFeature: function(feature) {
46 | return feature._id;
47 | },
48 | ```
49 |
50 | * `filter` - **{function}** The filter function gets called when iterating though each vector tile feature (vtf). You have access to every property associated with a given feature (the feature, and the layer). You can also filter based of the context (each tile that the feature is drawn onto). Returning false skips over the feature and it is not drawn. **Required**.
51 | * *@param feature* *@returns {boolean}*
52 |
53 | ```js
54 | filter: function(feature, context) {
55 | if (feature.layer.name === 'gaul_2014_adm1' || feature.layer.name === 'gaul_2014_adm1_label') {
56 | return true;
57 | }
58 | return false;
59 | },
60 | ```
61 |
62 | * `style` - **{function}** This function sets properties that the HTML5 Canvas' context uses to draw on the map. If you do not specify this, default styling will be applied to your features. `style.selected` parameters specify how a feature looks when it is selected. **Optional**.
63 | * *@returns {object}*
64 |
65 | ```js
66 | style: function (feature) {
67 | var style = {};
68 |
69 | var type = feature.type;
70 | switch (type) {
71 | case 1: //'Point'
72 | style.color = 'rgba(49,79,79,1)';
73 | style.radius = 5;
74 | style.selected = {
75 | color: 'rgba(255,255,0,0.5)',
76 | radius: 6
77 | };
78 | break;
79 | case 2: //'LineString'
80 | style.color = 'rgba(161,217,155,0.8)';
81 | style.size = 3;
82 | style.selected = {
83 | color: 'rgba(255,25,0,0.5)',
84 | size: 4
85 | };
86 | break;
87 | case 3: //'Polygon'
88 | style.color = 'rgba(149,139,255,0.4)';
89 | style.outline = {
90 | color: 'rgb(20,20,20)',
91 | size: 1
92 | };
93 | style.selected = {
94 | color: 'rgba(255,140,0,0.3)',
95 | outline: {
96 | color: 'rgba(255,140,0,1)',
97 | size: 2
98 | }
99 | };
100 | break;
101 | }
102 |
103 | if (feature.layer.name === 'gaul_2014_adm1_label') {
104 | style.ajaxSource = function(mvtFeature) {
105 | var id = mvtFeature.id;
106 | return 'http://localhost:8888/fsp/2014/fsp/aggregations-no-name/' + id + '.json';
107 | };
108 |
109 | style.staticLabel = function(mvtFeature, ajaxData) {
110 | var style = {
111 | html: ajaxData.total_count,
112 | iconSize: [33,33],
113 | cssClass: 'label-icon-number',
114 | cssSelectedClass: 'label-icon-number-selected'
115 | };
116 | return style;
117 | };
118 | }
119 |
120 | return style;
121 | },
122 | ```
123 |
124 | * `layerLink` - **{function}** When we want to link events between layers, like clicking on a label and a
125 | corresponding polygon feature, this will return the corresponding mapping
126 | between layers. This provides knowledge of which other feature a given feature
127 | is linked to. **Optional**.
128 | * *@param layerName* - the layer we want to know the linked layer from
129 | * *@returns {string}* - returns corresponding linked layer.
130 |
131 | ```js
132 | layerLink: function(layerName) {
133 | if (layerName.indexOf('_label') > -1) {
134 | return layerName.replace('_label','');
135 | }
136 | return layerName + '_label';
137 | }
138 | ```
139 |
140 | * 'onClick' - **{function}** This callback is fired every time a layer in clickableLayers is clicked on.
141 | * *@param event* - the event that initiated the click. This event object is the Leaflet event along with the feature object.
142 |
143 | ```js
144 | onClick: function(evt) {
145 | console.log('click');
146 | }
147 | ```
148 |
149 | * 'scope' - **{object}** The execution context for the onClick callback
150 |
151 | * 'xhrHeaders' - **{object}** The header and value to set on the XMLHttpRequest when fetching tiles.
152 |
153 | ```json
154 | {
155 | "Authorization": "xxxxxxxxxxx"
156 | }
157 | ```
158 |
--------------------------------------------------------------------------------
/examples/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Basic
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/basic.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([-5, 27.4], 5); // africa
4 |
5 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
6 | maxZoom: 18,
7 | id: 'examples.map-i86knfo3'
8 | }).addTo(map);
9 |
10 |
11 | var mvtSource = new L.TileLayer.MVTSource({
12 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/GAUL_FSP/{z}/{x}/{y}.pbf",
13 | debug: true,
14 | clickableLayers: ["GAUL0"],
15 | getIDForLayerFeature: function(feature) {
16 | return feature.properties.id;
17 | },
18 |
19 | /**
20 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
21 | * to every property associated with a given feature (the feature, and the layer). You can also filter
22 | * based of the context (each tile that the feature is drawn onto).
23 | *
24 | * Returning false skips over the feature and it is not drawn.
25 | *
26 | * @param feature
27 | * @returns {boolean}
28 | */
29 | filter: function(feature, context) {
30 | if (feature.layer.name === 'GAUL0') {
31 | return true;
32 | }
33 | return false;
34 | },
35 |
36 | style: function (feature) {
37 | var style = {};
38 |
39 | var type = feature.type;
40 | switch (type) {
41 | case 1: //'Point'
42 | style.color = 'rgba(49,79,79,1)';
43 | style.radius = 5;
44 | style.selected = {
45 | color: 'rgba(255,255,0,0.5)',
46 | radius: 6
47 | };
48 | break;
49 | case 2: //'LineString'
50 | style.color = 'rgba(161,217,155,0.8)';
51 | style.size = 3;
52 | style.selected = {
53 | color: 'rgba(255,25,0,0.5)',
54 | size: 4
55 | };
56 | break;
57 | case 3: //'Polygon'
58 | style.color = fillColor;
59 | style.outline = {
60 | color: strokeColor,
61 | size: 1
62 | };
63 | style.selected = {
64 | color: 'rgba(255,140,0,0.3)',
65 | outline: {
66 | color: 'rgba(255,140,0,1)',
67 | size: 2
68 | }
69 | };
70 | break;
71 | }
72 | return style;
73 | }
74 |
75 | });
76 | debug.mvtSource = mvtSource;
77 |
78 | //Globals that we can change later.
79 | var fillColor = 'rgba(149,139,255,0.4)';
80 | var strokeColor = 'rgb(20,20,20)';
81 |
82 | //Add layer
83 | map.addLayer(mvtSource);
84 |
--------------------------------------------------------------------------------
/examples/confetti.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Basic
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/confetti.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([21, 80], 5); // India
4 |
5 | L.tileLayer('http://{s}.tiles.mapbox.com/v3/spatialdev.map-c9z2cyef/{z}/{x}/{y}.png', {
6 | maxZoom: 18,
7 | attribution: 'Map data © OpenStreetMap contributors, ' +
8 | 'CC-BY-SA, ' +
9 | 'Imagery © Mapbox',
10 | id: 'examples.map-i86knfo3'
11 | }).addTo(map);
12 |
13 |
14 | var pbfSource = new L.TileLayer.MVTSource({
15 | url: "http://spatialserver.spatialdev.com/services/postgis/cicos_2014/geom/vector-tiles/{z}/{x}/{y}.pbf?fields=type,id",
16 | debug: false,
17 | clickableLayers: null,
18 |
19 | getIDForLayerFeature: function(feature) {
20 | return feature.properties.id;
21 | },
22 |
23 | /**
24 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
25 | * to every property associated with a given feature (the feature, and the layer). You can also filter
26 | * based of the context (each tile that the feature is drawn onto).
27 | *
28 | * Returning false skips over the feature and it is not drawn.
29 | *
30 | * @param feature
31 | * @returns {boolean}
32 | */
33 | filter: function(feature, context) {
34 | //return feature.properties.type != 'Mobile Money Agent';
35 | return true;
36 | },
37 |
38 | /**
39 | * When we want to link events between layers, like clicking on a label and a
40 | * corresponding polygon freature, this will return the corresponding mapping
41 | * between layers. This provides knowledge of which other feature a given feature
42 | * is linked to.
43 | *
44 | * @param layerName the layer we want to know the linked layer from
45 | * @returns {string} returns corresponding linked layer
46 | */
47 | layerLink: function(layerName) {
48 | if (layerName.indexOf('_label') > -1) {
49 | return layerName.replace('_label', '');
50 | }
51 | return layerName + '_label';
52 | },
53 |
54 | /**
55 | * Specify which features should have a certain z index (integer). Lower numbers will draw on 'the bottom'.
56 | *
57 | * @param feature - the PBFFeature that contains properties
58 | */
59 | layerOrdering: function(feature) {
60 | //This only needs to be done for each type, not necessarily for each feature. But we'll start here.
61 | if (feature && feature.properties) {
62 | feature.properties.zIndex = CICO_LAYERS[feature.properties.type].zIndex || 5;
63 | }
64 | },
65 |
66 | style: function(feature) {
67 | var style = {};
68 | var selected = style.selected = {};
69 | var pointRadius = 1;
70 |
71 | function ScaleDependentPointRadius(zoom) {
72 | //Set point radius based on zoom
73 | var pointRadius = 1;
74 | if (zoom >= 0 && zoom <= 7) {
75 | pointRadius = 1;
76 | }
77 | else if (zoom > 7 && zoom <= 10) {
78 | pointRadius = 2;
79 | }
80 | else if (zoom > 10) {
81 | pointRadius = 3;
82 | }
83 |
84 | return pointRadius;
85 | }
86 |
87 | var type = feature.type;
88 | switch (type) {
89 | case 1: //'Point'
90 | // unselected
91 | style.color = CICO_LAYERS[feature.properties.type].color || '#3086AB';
92 | style.radius = ScaleDependentPointRadius;
93 | // selected
94 | style.selected = {
95 | color: 'rgba(255,255,0,0.5)',
96 | radius: 6
97 | };
98 | break;
99 | case 2: //'LineString'
100 | // unselected
101 | style.color = 'rgba(161,217,155,0.8)';
102 | style.size = 3;
103 | // selected
104 | style.selected = {
105 | color: 'rgba(255,255,0,0.5)',
106 | size: 6
107 | };
108 | break;
109 | case 3: //'Polygon'
110 | // unselected
111 | style.color = 'rgba(149,139,255,0.4)';
112 | style.outline = {
113 | color: 'rgb(20,20,20)',
114 | size: 2
115 | };
116 | // selected
117 | style.selected = {
118 | color: 'rgba(255,255,0,0.5)',
119 | outline: {
120 | color: '#d9534f',
121 | size: 3
122 | }
123 | };
124 |
125 | }
126 |
127 | return style;
128 | },
129 |
130 | onClick: function(evt) {
131 | console.log('clickkkk');
132 | }
133 |
134 | });
135 |
136 | debug.mvtSource = pbfSource;
137 |
138 |
139 | //Add layer
140 | map.addLayer(pbfSource);
141 |
142 |
143 | var CICO_LAYERS = {
144 | 'Offsite ATMs': {
145 | color: '#3086AB',
146 | infoLabel: 'Offsite ATM',
147 | providers: null,
148 | zIndex: 6
149 | },
150 | 'Bank Branches': {
151 | color: '#977C00',
152 | infoLabel: 'Bank Branch',
153 | providers: null,
154 | zIndex: 5
155 | },
156 | 'MFIs': {
157 | color: '#9B242D',
158 | infoLabel: 'MFI',
159 | providers: null,
160 | zIndex: 7
161 | },
162 | 'SACCOs': {
163 | color: '#cf8a57',
164 | infoLabel: 'SACCO',
165 | providers: null,
166 | zIndex: 10
167 | },
168 | 'Mobile Money Agent': {
169 | color: '#8CB7C7',
170 | infoLabel: 'Mobile Money Agent',
171 | providers: null,
172 | zIndex: -1
173 | },
174 | 'MDIs': {
175 | color: '#825D99',
176 | infoLabel: 'MDI',
177 | providers: null,
178 | zIndex: 6
179 | },
180 | 'Credit Institution': {
181 | color: '#6CA76B',
182 | infoLabel: 'Credit Institution',
183 | providers: null,
184 | zIndex: 5
185 | },
186 | 'MFBs': {
187 | color: '#825D99',
188 | infoLabel: 'MFB',
189 | providers: null,
190 | zIndex: 7
191 | },
192 | 'Motor Parks': {
193 | color: '#bd85b3',
194 | infoLabel: 'Motor Parks',
195 | providers: null,
196 | zIndex: 7
197 | },
198 | 'Mobile Network Operator Outlets': {
199 | color: '#a2a2a2',
200 | infoLabel: 'Mobile Network Operator Outlets',
201 | providers: null,
202 | zIndex: 0
203 | },
204 | 'Post Offices': {
205 | color: '#FFFF00',
206 | infoLabel: 'Post Offices',
207 | providers: null,
208 | zIndex: 4
209 | },
210 | 'Post Office': {
211 | color: '#80ad7b',
212 | infoLabel: 'Post Offices',
213 | providers: null,
214 | zIndex: 6
215 | },
216 | 'Bus Stand': {
217 | color: '#80ad7b',
218 | infoLabel: 'Bus Stands',
219 | providers: null,
220 | zIndex: 6
221 | },
222 | 'Bus Stands': {
223 | color: '#80ad7b',
224 | infoLabel: 'Bus Stands',
225 | providers: null,
226 | zIndex: 6
227 | },
228 |
229 | //Kenya
230 | 'Insurance Providers': {
231 | color: '#3086AB',
232 | infoLabel: 'Insurance providers',
233 | providers: null,
234 | zIndex: 6
235 | },
236 | 'Money Transfer Service': {
237 | color: '#977C00',
238 | infoLabel: 'Money Transfer Service',
239 | providers: null,
240 | zIndex: 6
241 | },
242 | 'Dev Finance': {
243 | color: '#9B242D',
244 | infoLabel: 'Dev Finance',
245 | providers: null,
246 | zIndex: 6
247 | },
248 | 'Forex Bureaus': {
249 | color: '#cf8a57',
250 | infoLabel: 'Forex Bureaus',
251 | providers: null,
252 | zIndex: 6
253 | },
254 | 'Cap Markets': {
255 | color: '#825D99',
256 | infoLabel: 'Cap Markets',
257 | providers: null,
258 | zIndex: 6
259 | },
260 | 'Pension Providers': {
261 | color: '#a2a2a2',
262 | infoLabel: 'Pension providers',
263 | providers: null,
264 | zIndex: 6
265 | },
266 | 'Purchase Lease Factoring': {
267 | color: '#80ad7b',
268 | infoLabel: 'Purchase Lease Factoring',
269 | providers: null,
270 | zIndex: 6
271 | },
272 | 'Bank Agent': {
273 | color: '#80ad7b',
274 | infoLabel: 'Bank Agent',
275 | providers: null,
276 | zIndex: 6
277 | },
278 | 'Bank and Mortgage': {
279 | color: '#80ad7b',
280 | infoLabel: 'Banks and Mortgage',
281 | providers: null,
282 | zIndex: 6
283 | },
284 | 'Commercial Bank': {
285 | color: '#80ad7b',
286 | infoLabel: 'Commercial Bank',
287 | providers: null,
288 | zIndex: 3
289 | },
290 |
291 | 'PostBank': {
292 | color: '#bd85b3',
293 | infoLabel: 'Post Bank',
294 | providers: null,
295 | zIndex: 6
296 | },
297 |
298 | //Nigeria New Post Offices
299 | 'NIPOST Post Office': {
300 | color: '#80ad7b',
301 | infoLabel: 'NIPOST Post Offices',
302 | providers: null,
303 | zIndex: 6
304 | },
305 | 'NIPOST Post Shop': {
306 | color: '#80ad7b',
307 | infoLabel: 'NIPOST Post Shops',
308 | providers: null,
309 | zIndex: 6
310 | },
311 | 'NIPOST Postal Agency': {
312 | color: '#80ad7b',
313 | infoLabel: 'NIPOST Postal Agencies',
314 | providers: null,
315 | zIndex: 6
316 | },
317 |
318 | //India
319 | 'Postal Outlets': {
320 | color: '#E6DC00',
321 | infoLabel: 'Postal Outlets',
322 | providers: null,
323 | zIndex: 3
324 | },
325 | 'Commercial Banks': {
326 | color: '#80ad7b',
327 | infoLabel: 'Commercial Banks',
328 | providers: null,
329 | zIndex: 2
330 | },
331 | 'Bank Customer Service Points': {
332 | color: '#977C00',
333 | infoLabel: 'Bank Customer Service Points',
334 | providers: null,
335 | zIndex: 4
336 | }
337 | };
--------------------------------------------------------------------------------
/examples/dynamic-label.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dynamic Label
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
89 |
90 |
91 |
92 |
93 |
Vector Tiles Style:
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | style
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
368 |
369 |
370 |
371 |
372 |
--------------------------------------------------------------------------------
/examples/gadm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | GADM
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/gadm.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([47.6, -122.3], 9); // Seattle
4 |
5 | L.tileLayer('http://{s}.tiles.mapbox.com/v3/spatialdev.map-c9z2cyef/{z}/{x}/{y}.png', {
6 | maxZoom: 18,
7 | attribution: 'Map data © OpenStreetMap contributors, ' +
8 | 'CC-BY-SA, ' +
9 | 'Imagery © Mapbox',
10 | id: 'examples.map-i86knfo3'
11 | }).addTo(map);
12 |
13 |
14 | var pbfSource = new L.TileLayer.MVTSource({
15 | url: "http://localhost:3000/services/vector-tiles/gadm/{z}/{x}/{y}.pbf",
16 | debug: false,
17 | clickableLayers: null,
18 |
19 | getIDForLayerFeature: function(feature) {
20 | return feature.properties.id;
21 | },
22 |
23 | /**
24 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
25 | * to every property associated with a given feature (the feature, and the layer). You can also filter
26 | * based of the context (each tile that the feature is drawn onto).
27 | *
28 | * Returning false skips over the feature and it is not drawn.
29 | *
30 | * @param feature
31 | * @returns {boolean}
32 | */
33 | filter: function(feature, context) {
34 | if (feature.layer.name === 'gadm2') {
35 | return true;
36 | }
37 | return false;
38 | },
39 |
40 | /**
41 | * When we want to link events between layers, like clicking on a label and a
42 | * corresponding polygon freature, this will return the corresponding mapping
43 | * between layers. This provides knowledge of which other feature a given feature
44 | * is linked to.
45 | *
46 | * @param layerName the layer we want to know the linked layer from
47 | * @returns {string} returns corresponding linked layer
48 | */
49 | layerLink: function(layerName) {
50 | if (layerName.indexOf('_label') > -1) {
51 | return layerName.replace('_label', '');
52 | }
53 | return layerName + '_label';
54 | },
55 |
56 | /**
57 | * Specify which features should have a certain z index (integer). Lower numbers will draw on 'the bottom'.
58 | *
59 | * @param feature - the PBFFeature that contains properties
60 | */
61 | layerOrdering: function(feature) {
62 | //This only needs to be done for each type, not necessarily for each feature. But we'll start here.
63 | },
64 |
65 | style: function(feature) {
66 | var style = {};
67 | var selected = style.selected = {};
68 | var pointRadius = 1;
69 |
70 | function ScaleDependentPointRadius(zoom) {
71 | //Set point radius based on zoom
72 | var pointRadius = 1;
73 | if (zoom >= 0 && zoom <= 7) {
74 | pointRadius = 1;
75 | }
76 | else if (zoom > 7 && zoom <= 10) {
77 | pointRadius = 2;
78 | }
79 | else if (zoom > 10) {
80 | pointRadius = 3;
81 | }
82 |
83 | return pointRadius;
84 | }
85 |
86 | var type = feature.type;
87 | switch (type) {
88 | case 1: //'Point'
89 | // unselected
90 | style.color = CICO_LAYERS[feature.properties.type].color || '#3086AB';
91 | style.radius = ScaleDependentPointRadius;
92 | // selected
93 | style.selected = {
94 | color: 'rgba(255,255,0,0.5)',
95 | radius: 6
96 | };
97 | break;
98 | case 2: //'LineString'
99 | // unselected
100 | style.color = 'rgba(161,217,155,0.8)';
101 | style.size = 3;
102 | // selected
103 | style.selected = {
104 | color: 'rgba(255,255,0,0.5)',
105 | size: 6
106 | };
107 | break;
108 | case 3: //'Polygon'
109 | // unselected
110 | style.color = 'rgba(149,139,255,0.4)';
111 | style.outline = {
112 | color: 'rgb(20,20,20)',
113 | size: 2
114 | };
115 | // selected
116 | style.selected = {
117 | color: 'rgba(255,255,0,0.5)',
118 | outline: {
119 | color: '#d9534f',
120 | size: 3
121 | }
122 | };
123 |
124 | }
125 |
126 | return style;
127 | },
128 |
129 | onClick: function(evt) {
130 | console.log('clickkkk');
131 | }
132 |
133 | });
134 |
135 | debug.mvtSource = pbfSource;
136 |
137 |
138 | //Add layer
139 | map.addLayer(pbfSource);
140 |
--------------------------------------------------------------------------------
/examples/india-aggregations.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | India Aggregations
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/india-aggregations.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([25.40,79.409], 6); // Northern India
4 |
5 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
6 | maxZoom: 18
7 | }).addTo(map);
8 |
9 |
10 | var mvtSource = new L.TileLayer.MVTSource({
11 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gaul_fsp_india/{z}/{x}/{y}.pbf",
12 | debug: true,
13 | clickableLayers: ['gaul_2014_adm1'],
14 |
15 | /**
16 | * If you click on a feature, if there is a different
17 | * currently selected feature, that gets toggled off.
18 | */
19 | mutexToggle: true,
20 |
21 | getIDForLayerFeature: function(feature) {
22 | return feature._id;
23 | },
24 |
25 | /**
26 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
27 | * to every property associated with a given feature (the feature, and the layer). You can also filter
28 | * based of the context (each tile that the feature is drawn onto).
29 | *
30 | * Returning false skips over the feature and it is not drawn.
31 | *
32 | * @param feature
33 | * @returns {boolean}
34 | */
35 | filter: function(feature, context) {
36 | if (feature.layer.name === 'gaul_2014_adm1' || feature.layer.name === 'gaul_2014_adm1_label') {
37 | return true;
38 | }
39 | return false;
40 | },
41 |
42 | style: function (feature) {
43 | var style = {};
44 |
45 | var type = feature.type;
46 | switch (type) {
47 | case 1: //'Point'
48 | style.color = 'rgba(49,79,79,1)';
49 | style.radius = 5;
50 | style.selected = {
51 | color: 'rgba(255,255,0,0.5)',
52 | radius: 6
53 | };
54 | break;
55 | case 2: //'LineString'
56 | style.color = 'rgba(161,217,155,0.8)';
57 | style.size = 3;
58 | style.selected = {
59 | color: 'rgba(255,25,0,0.5)',
60 | size: 4
61 | };
62 | break;
63 | case 3: //'Polygon'
64 | style.color = 'rgba(149,139,255,0.4)';
65 | style.outline = {
66 | color: 'rgb(20,20,20)',
67 | size: 1
68 | };
69 | style.selected = {
70 | color: 'rgba(255,140,0,0.3)',
71 | outline: {
72 | color: 'rgba(255,140,0,1)',
73 | size: 2
74 | }
75 | };
76 | break;
77 | }
78 |
79 | if (feature.layer.name === 'gaul_2014_adm1_label') {
80 | style.ajaxSource = function(mvtFeature) {
81 | var id = mvtFeature.id;
82 | return 'http://spatialserver.spatialdev.com/fsp/2014/fsp/aggregations-no-name/' + id + '.json';
83 | };
84 |
85 | style.staticLabel = function(mvtFeature, ajaxData) {
86 | var style = {
87 | html: ajaxData.total_count,
88 | iconSize: [33,33],
89 | cssClass: 'label-icon-number',
90 | cssSelectedClass: 'label-icon-number-selected'
91 | };
92 | return style;
93 | };
94 | }
95 |
96 | return style;
97 | },
98 |
99 | /**
100 | * When we want to link events between layers, like clicking on a label and a
101 | * corresponding polygon freature, this will return the corresponding mapping
102 | * between layers. This provides knowledge of which other feature a given feature
103 | * is linked to.
104 | *
105 | * @param layerName the layer we want to know the linked layer from
106 | * @returns {string} returns corresponding linked layer
107 | */
108 | layerLink: function(layerName) {
109 | if (layerName.indexOf('_label') > -1) {
110 | return layerName.replace('_label','');
111 | }
112 | return layerName + '_label';
113 | }
114 |
115 | });
116 | debug.mvtSource = mvtSource;
117 |
118 | //Add layer
119 | map.addLayer(mvtSource);
120 |
--------------------------------------------------------------------------------
/examples/india-label.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | India Aggregations
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/india-label.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([25.40,79.409], 6); // Northern India
4 |
5 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
6 | maxZoom: 18
7 | }).addTo(map);
8 |
9 |
10 | var mvtSource = new L.TileLayer.MVTSource({
11 | // alternative mapbox web service source, gives lots of 404 errors as mapbox likes to do...
12 | // url: "https://a.tiles.mapbox.com/v4/nicholashallahan.43cc7605/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoibmljaG9sYXNoYWxsYWhhbiIsImEiOiJ5YWxaRUY0In0.qLtNgKJKXvhm7j5u6ZvDDw",
13 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gaul_fsp_india/{z}/{x}/{y}.pbf",
14 | debug: true,
15 | clickableLayers: ['gaul_2014_adm1'],
16 | getIDForLayerFeature: function(feature) {
17 | return feature._id;
18 | },
19 |
20 | /**
21 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
22 | * to every property associated with a given feature (the feature, and the layer). You can also filter
23 | * based of the context (each tile that the feature is drawn onto).
24 | *
25 | * Returning false skips over the feature and it is not drawn.
26 | *
27 | * @param feature
28 | * @returns {boolean}
29 | */
30 | filter: function(feature, context) {
31 | if (feature.layer.name === 'gaul_2014_adm1' || feature.layer.name === 'gaul_2014_adm1_label') {
32 | return true;
33 | }
34 | return false;
35 | },
36 |
37 | style: function (feature) {
38 | var style = {};
39 |
40 | var type = feature.type;
41 | switch (type) {
42 | case 1: //'Point'
43 | style.color = 'rgba(49,79,79,1)';
44 | style.radius = 5;
45 | style.selected = {
46 | color: 'rgba(255,255,0,0.5)',
47 | radius: 6
48 | };
49 | break;
50 | case 2: //'LineString'
51 | style.color = 'rgba(161,217,155,0.8)';
52 | style.size = 3;
53 | style.selected = {
54 | color: 'rgba(255,25,0,0.5)',
55 | size: 4
56 | };
57 | break;
58 | case 3: //'Polygon'
59 | style.color = 'rgba(149,139,255,0.4)';
60 | style.outline = {
61 | color: 'rgb(20,20,20)',
62 | size: 1
63 | };
64 | style.selected = {
65 | color: 'rgba(255,140,0,0.3)',
66 | outline: {
67 | color: 'rgba(255,140,0,1)',
68 | size: 2
69 | }
70 | };
71 | break;
72 | }
73 |
74 | if (feature.layer.name === 'gaul_2014_adm1_label') {
75 | style.staticLabel = function() {
76 | var style = {
77 | html: feature.properties.name,
78 | iconSize: [125,30],
79 | cssClass: 'label-icon-text'
80 | };
81 | return style;
82 | };
83 | }
84 |
85 | return style;
86 | },
87 |
88 | /**
89 | * When we want to link events between layers, like clicking on a label and a
90 | * corresponding polygon freature, this will return the corresponding mapping
91 | * between layers. This provides knowledge of which other feature a given feature
92 | * is linked to.
93 | *
94 | * @param layerName the layer we want to know the linked layer from
95 | * @returns {string} returns corresponding linked layer
96 | */
97 | layerLink: function(layerName) {
98 | if (layerName.indexOf('_label') > -1) {
99 | return layerName.replace('_label','');
100 | }
101 | return layerName + '_label';
102 | },
103 |
104 | /**
105 | * Callback function that fires whenever a clickable feature is clicked on the map.
106 | * @param evt
107 | */
108 | onClick: function(evt) {
109 | console.log('click');
110 | }
111 |
112 | });
113 | debug.mvtSource = mvtSource;
114 |
115 | //Add layer
116 | map.addLayer(mvtSource);
117 |
--------------------------------------------------------------------------------
/examples/mapbox-source.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Mapbox Source
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/mapbox-source.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([47.651737, -122.30754], 16); // University of Washington
4 |
5 | L.tileLayer('https://a.tiles.mapbox.com/v3/cugos.jolef8gc/{z}/{x}/{y}.png', {
6 | maxZoom: 18
7 | }).addTo(map);
8 |
9 |
10 | var mvtSource = new L.TileLayer.MVTSource({
11 | // alternative mapbox web service source, gives lots of 404 errors as mapbox likes to do...
12 | // url: "https://a.tiles.mapbox.com/v4/nicholashallahan.43cc7605/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoibmljaG9sYXNoYWxsYWhhbiIsImEiOiJ5YWxaRUY0In0.qLtNgKJKXvhm7j5u6ZvDDw",
13 | url: "https://a.tiles.mapbox.com/v4/mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v6-dev/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6IlhHVkZmaW8ifQ.hAMX5hSW-QnTeRCMAy9A8Q",
14 | debug: false,
15 | clickableLayers: ['building'],
16 | getIDForLayerFeature: function(feature) {
17 | return feature.id;
18 | },
19 |
20 | /**
21 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
22 | * to every property associated with a given feature (the feature, and the layer). You can also filter
23 | * based of the context (each tile that the feature is drawn onto).
24 | *
25 | * Returning false skips over the feature and it is not drawn.
26 | *
27 | * @param feature
28 | * @returns {boolean}
29 | */
30 | filter: function(feature, context) {
31 | return true;
32 | },
33 |
34 | style: function (feature) {
35 | var style = {};
36 |
37 | var type = feature.type;
38 | switch (type) {
39 | case 1: //'Point'
40 | style.color = 'rgba(49,79,79,0.2)';
41 | style.radius = 5;
42 | style.selected = {
43 | color: 'rgba(255,255,0,0.5)',
44 | radius: 6
45 | };
46 | break;
47 | case 2: //'LineString'
48 | style.color = 'rgba(161,217,155,0.8)';
49 | style.size = 3;
50 | style.selected = {
51 | color: 'rgba(255,25,0,0.5)',
52 | size: 4
53 | };
54 | break;
55 | case 3: //'Polygon'
56 | style.color = 'rgba(149,139,255,0.4)';
57 | style.outline = {
58 | color: 'rgb(20,20,20)',
59 | size: 1
60 | };
61 | style.selected = {
62 | color: 'rgba(255,140,0,0.3)',
63 | outline: {
64 | color: 'rgba(255,140,0,1)',
65 | size: 2
66 | }
67 | };
68 | break;
69 | }
70 |
71 | if (feature.layer.name === 'poi_label') {
72 | style.staticLabel = function() {
73 | var style = {
74 | html: feature.properties.name,
75 | iconSize: [125,30],
76 | cssClass: 'label-icon-text'
77 | };
78 | return style;
79 | };
80 | }
81 |
82 | return style;
83 | },
84 |
85 | /**
86 | * When we want to link events between layers, like clicking on a label and a
87 | * corresponding polygon freature, this will return the corresponding mapping
88 | * between layers. This provides knowledge of which other feature a given feature
89 | * is linked to.
90 | *
91 | * @param layerName the layer we want to know the linked layer from
92 | * @returns {string} returns corresponding linked layer
93 | */
94 | layerLink: function(layerName) {
95 | if (layerName.indexOf('_label') > -1) {
96 | return layerName.replace('_label','');
97 | }
98 | return layerName + '_label';
99 | },
100 |
101 | /**
102 | * Callback function that fires whenever a clickable feature is clicked on the map.
103 | * @param evt
104 | */
105 | onClick: function(evt) {
106 | console.log('click');
107 | }
108 |
109 | });
110 | debug.mvtSource = mvtSource;
111 |
112 | //Add layer
113 | map.addLayer(mvtSource);
114 |
--------------------------------------------------------------------------------
/examples/osm_seattle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Basic
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/osm_seattle.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([21, 80], 5); // India
4 |
5 | L.tileLayer('http://{s}.tiles.mapbox.com/v3/spatialdev.map-c9z2cyef/{z}/{x}/{y}.png', {
6 | maxZoom: 18,
7 | attribution: 'Map data © OpenStreetMap contributors, ' +
8 | 'CC-BY-SA, ' +
9 | 'Imagery © Mapbox',
10 | id: 'examples.map-i86knfo3'
11 | }).addTo(map);
12 |
13 |
14 | var pbfSource = new L.TileLayer.MVTSource({
15 | url: "http://spatialserver.spatialdev.com/services/postgis/cicos_2014/geom/vector-tiles/{z}/{x}/{y}.pbf?fields=type,id",
16 | debug: false,
17 | clickableLayers: null,
18 |
19 | getIDForLayerFeature: function(feature) {
20 | return feature.properties.id;
21 | },
22 |
23 | /**
24 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
25 | * to every property associated with a given feature (the feature, and the layer). You can also filter
26 | * based of the context (each tile that the feature is drawn onto).
27 | *
28 | * Returning false skips over the feature and it is not drawn.
29 | *
30 | * @param feature
31 | * @returns {boolean}
32 | */
33 | filter: function(feature, context) {
34 | //return feature.properties.type != 'Mobile Money Agent';
35 | return true;
36 | },
37 |
38 | /**
39 | * When we want to link events between layers, like clicking on a label and a
40 | * corresponding polygon freature, this will return the corresponding mapping
41 | * between layers. This provides knowledge of which other feature a given feature
42 | * is linked to.
43 | *
44 | * @param layerName the layer we want to know the linked layer from
45 | * @returns {string} returns corresponding linked layer
46 | */
47 | layerLink: function(layerName) {
48 | if (layerName.indexOf('_label') > -1) {
49 | return layerName.replace('_label', '');
50 | }
51 | return layerName + '_label';
52 | },
53 |
54 | /**
55 | * Specify which features should have a certain z index (integer). Lower numbers will draw on 'the bottom'.
56 | *
57 | * @param feature - the PBFFeature that contains properties
58 | */
59 | layerOrdering: function(feature) {
60 | //This only needs to be done for each type, not necessarily for each feature. But we'll start here.
61 | if (feature && feature.properties) {
62 | feature.properties.zIndex = CICO_LAYERS[feature.properties.type].zIndex || 5;
63 | }
64 | },
65 |
66 | style: function(feature) {
67 | var style = {};
68 | var selected = style.selected = {};
69 | var pointRadius = 1;
70 |
71 | function ScaleDependentPointRadius(zoom) {
72 | //Set point radius based on zoom
73 | var pointRadius = 1;
74 | if (zoom >= 0 && zoom <= 7) {
75 | pointRadius = 1;
76 | }
77 | else if (zoom > 7 && zoom <= 10) {
78 | pointRadius = 2;
79 | }
80 | else if (zoom > 10) {
81 | pointRadius = 3;
82 | }
83 |
84 | return pointRadius;
85 | }
86 |
87 | var type = feature.type;
88 | switch (type) {
89 | case 1: //'Point'
90 | // unselected
91 | style.color = CICO_LAYERS[feature.properties.type].color || '#3086AB';
92 | style.radius = ScaleDependentPointRadius;
93 | // selected
94 | style.selected = {
95 | color: 'rgba(255,255,0,0.5)',
96 | radius: 6
97 | };
98 | break;
99 | case 2: //'LineString'
100 | // unselected
101 | style.color = 'rgba(161,217,155,0.8)';
102 | style.size = 3;
103 | // selected
104 | style.selected = {
105 | color: 'rgba(255,255,0,0.5)',
106 | size: 6
107 | };
108 | break;
109 | case 3: //'Polygon'
110 | // unselected
111 | style.color = 'rgba(149,139,255,0.4)';
112 | style.outline = {
113 | color: 'rgb(20,20,20)',
114 | size: 2
115 | };
116 | // selected
117 | style.selected = {
118 | color: 'rgba(255,255,0,0.5)',
119 | outline: {
120 | color: '#d9534f',
121 | size: 3
122 | }
123 | };
124 |
125 | }
126 |
127 | return style;
128 | },
129 |
130 | onClick: function(evt) {
131 | console.log('clickkkk');
132 | }
133 |
134 | });
135 |
136 | debug.mvtSource = pbfSource;
137 |
138 |
139 | //Add layer
140 | map.addLayer(pbfSource);
141 |
142 |
143 | var CICO_LAYERS = {
144 | 'Offsite ATMs': {
145 | color: '#3086AB',
146 | infoLabel: 'Offsite ATM',
147 | providers: null,
148 | zIndex: 6
149 | },
150 | 'Bank Branches': {
151 | color: '#977C00',
152 | infoLabel: 'Bank Branch',
153 | providers: null,
154 | zIndex: 5
155 | },
156 | 'MFIs': {
157 | color: '#9B242D',
158 | infoLabel: 'MFI',
159 | providers: null,
160 | zIndex: 7
161 | },
162 | 'SACCOs': {
163 | color: '#cf8a57',
164 | infoLabel: 'SACCO',
165 | providers: null,
166 | zIndex: 10
167 | },
168 | 'Mobile Money Agent': {
169 | color: '#8CB7C7',
170 | infoLabel: 'Mobile Money Agent',
171 | providers: null,
172 | zIndex: -1
173 | },
174 | 'MDIs': {
175 | color: '#825D99',
176 | infoLabel: 'MDI',
177 | providers: null,
178 | zIndex: 6
179 | },
180 | 'Credit Institution': {
181 | color: '#6CA76B',
182 | infoLabel: 'Credit Institution',
183 | providers: null,
184 | zIndex: 5
185 | },
186 | 'MFBs': {
187 | color: '#825D99',
188 | infoLabel: 'MFB',
189 | providers: null,
190 | zIndex: 7
191 | },
192 | 'Motor Parks': {
193 | color: '#bd85b3',
194 | infoLabel: 'Motor Parks',
195 | providers: null,
196 | zIndex: 7
197 | },
198 | 'Mobile Network Operator Outlets': {
199 | color: '#a2a2a2',
200 | infoLabel: 'Mobile Network Operator Outlets',
201 | providers: null,
202 | zIndex: 0
203 | },
204 | 'Post Offices': {
205 | color: '#FFFF00',
206 | infoLabel: 'Post Offices',
207 | providers: null,
208 | zIndex: 4
209 | },
210 | 'Post Office': {
211 | color: '#80ad7b',
212 | infoLabel: 'Post Offices',
213 | providers: null,
214 | zIndex: 6
215 | },
216 | 'Bus Stand': {
217 | color: '#80ad7b',
218 | infoLabel: 'Bus Stands',
219 | providers: null,
220 | zIndex: 6
221 | },
222 | 'Bus Stands': {
223 | color: '#80ad7b',
224 | infoLabel: 'Bus Stands',
225 | providers: null,
226 | zIndex: 6
227 | },
228 |
229 | //Kenya
230 | 'Insurance Providers': {
231 | color: '#3086AB',
232 | infoLabel: 'Insurance providers',
233 | providers: null,
234 | zIndex: 6
235 | },
236 | 'Money Transfer Service': {
237 | color: '#977C00',
238 | infoLabel: 'Money Transfer Service',
239 | providers: null,
240 | zIndex: 6
241 | },
242 | 'Dev Finance': {
243 | color: '#9B242D',
244 | infoLabel: 'Dev Finance',
245 | providers: null,
246 | zIndex: 6
247 | },
248 | 'Forex Bureaus': {
249 | color: '#cf8a57',
250 | infoLabel: 'Forex Bureaus',
251 | providers: null,
252 | zIndex: 6
253 | },
254 | 'Cap Markets': {
255 | color: '#825D99',
256 | infoLabel: 'Cap Markets',
257 | providers: null,
258 | zIndex: 6
259 | },
260 | 'Pension Providers': {
261 | color: '#a2a2a2',
262 | infoLabel: 'Pension providers',
263 | providers: null,
264 | zIndex: 6
265 | },
266 | 'Purchase Lease Factoring': {
267 | color: '#80ad7b',
268 | infoLabel: 'Purchase Lease Factoring',
269 | providers: null,
270 | zIndex: 6
271 | },
272 | 'Bank Agent': {
273 | color: '#80ad7b',
274 | infoLabel: 'Bank Agent',
275 | providers: null,
276 | zIndex: 6
277 | },
278 | 'Bank and Mortgage': {
279 | color: '#80ad7b',
280 | infoLabel: 'Banks and Mortgage',
281 | providers: null,
282 | zIndex: 6
283 | },
284 | 'Commercial Bank': {
285 | color: '#80ad7b',
286 | infoLabel: 'Commercial Bank',
287 | providers: null,
288 | zIndex: 3
289 | },
290 |
291 | 'PostBank': {
292 | color: '#bd85b3',
293 | infoLabel: 'Post Bank',
294 | providers: null,
295 | zIndex: 6
296 | },
297 |
298 | //Nigeria New Post Offices
299 | 'NIPOST Post Office': {
300 | color: '#80ad7b',
301 | infoLabel: 'NIPOST Post Offices',
302 | providers: null,
303 | zIndex: 6
304 | },
305 | 'NIPOST Post Shop': {
306 | color: '#80ad7b',
307 | infoLabel: 'NIPOST Post Shops',
308 | providers: null,
309 | zIndex: 6
310 | },
311 | 'NIPOST Postal Agency': {
312 | color: '#80ad7b',
313 | infoLabel: 'NIPOST Postal Agencies',
314 | providers: null,
315 | zIndex: 6
316 | },
317 |
318 | //India
319 | 'Postal Outlets': {
320 | color: '#E6DC00',
321 | infoLabel: 'Postal Outlets',
322 | providers: null,
323 | zIndex: 3
324 | },
325 | 'Commercial Banks': {
326 | color: '#80ad7b',
327 | infoLabel: 'Commercial Banks',
328 | providers: null,
329 | zIndex: 2
330 | },
331 | 'Bank Customer Service Points': {
332 | color: '#977C00',
333 | infoLabel: 'Bank Customer Service Points',
334 | providers: null,
335 | zIndex: 4
336 | }
337 | };
--------------------------------------------------------------------------------
/examples/static-label.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Static Label
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/examples/static-label.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Nicholas Hallahan
3 | * on 8/15/14.
4 | */
5 |
6 | var opts = {
7 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gadm2014kenya/{z}/{x}/{y}.pbf",
8 | debug: true,
9 | clickableLayers: ['gadm0', 'gadm1', 'gadm2', 'gadm3', 'gadm4', 'gadm5'],
10 |
11 | getIDForLayerFeature: function(feature) {
12 | return feature.properties.id;
13 | },
14 |
15 | /**
16 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
17 | * to every property associated with a given feature (the feature, and the layer). You can also filter
18 | * based of the context (each tile that the feature is drawn onto).
19 | *
20 | * Returning false skips over the feature and it is not drawn.
21 | *
22 | * @param feature
23 | * @returns {boolean}
24 | */
25 | filter: function(feature, context) {
26 | if (feature.layer.name === 'gadm1_label' || feature.layer.name === 'gadm1') {
27 | return true;
28 | }
29 |
30 | return false;
31 | },
32 |
33 | /**
34 | * When we want to link events between layers, like clicking on a label and a
35 | * corresponding polygon freature, this will return the corresponding mapping
36 | * between layers. This provides knowledge of which other feature a given feature
37 | * is linked to.
38 | *
39 | * @param layerName the layer we want to know the linked layer from
40 | * @returns {string} returns corresponding linked layer
41 | */
42 | layerLink: function(layerName) {
43 | if (layerName.indexOf('_label') > -1) {
44 | return layerName.replace('_label','');
45 | }
46 | return layerName + '_label';
47 | },
48 |
49 | style: function(feature) {
50 | var style = {};
51 | var selected = style.selected = {};
52 |
53 | var type = feature.type;
54 | switch (type) {
55 | case 1: //'Point'
56 | // unselected
57 | style.color = '#ff0000';
58 | style.radius = 3;
59 | // selected
60 | selected.color = 'rgba(255,255,0,0.5)';
61 | selected.radius = 5;
62 | break;
63 | case 2: //'LineString'
64 | // unselected
65 | style.color = 'rgba(161,217,155,0.8)';
66 | style.size = 3;
67 | // selected
68 | selected.color = 'rgba(255,25,0,0.5)';
69 | selected.size = 3;
70 | break;
71 | case 3: //'Polygon'
72 | // unselected
73 | style.color = 'rgba(149,139,255,0.4)';
74 | style.outline = {
75 | color: 'rgb(20,20,20)',
76 | size: 2
77 | };
78 | // selected
79 | selected.color = 'rgba(255,25,0,0.3)';
80 | selected.outline = {
81 | color: '#d9534f',
82 | size: 3
83 | };
84 | }
85 |
86 | if (feature.layer.name === 'gadm1_label') {
87 | style.staticLabel = function() {
88 | var style = {
89 | html: feature.properties.name,
90 | iconSize: [125,30],
91 | cssClass: 'label-icon-text'
92 | };
93 | return style;
94 | };
95 | }
96 |
97 | return style;
98 | }
99 | }
--------------------------------------------------------------------------------
/examples/visibleLayers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | India Aggregations
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/visibleLayers.js:
--------------------------------------------------------------------------------
1 | var debug = {};
2 |
3 | var map = L.map('map').setView([25.40,79.409], 6); // Northern India
4 |
5 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
6 | maxZoom: 18
7 | }).addTo(map);
8 |
9 |
10 | var mvtSource = new L.TileLayer.MVTSource({
11 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gaul_fsp_india/{z}/{x}/{y}.pbf",
12 | debug: true,
13 | clickableLayers: ['gaul_2014_adm1'],
14 | visibleLayers: ['gaul_2014_adm1'], //ONLY show this layer that's contained inside of these pbfs.
15 | getIDForLayerFeature: function(feature) {
16 | return feature._id;
17 | },
18 |
19 | /**
20 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
21 | * to every property associated with a given feature (the feature, and the layer). You can also filter
22 | * based of the context (each tile that the feature is drawn onto).
23 | *
24 | * Returning false skips over the feature and it is not drawn.
25 | *
26 | * @param feature
27 | * @returns {boolean}
28 | */
29 | filter: function(feature, context) {
30 | if (feature.layer.name === 'gaul_2014_adm1' || feature.layer.name === 'gaul_2014_adm1_label') {
31 | return true;
32 | }
33 | return false;
34 | },
35 |
36 | style: function (feature) {
37 | var style = {};
38 |
39 | var type = feature.type;
40 | switch (type) {
41 | case 1: //'Point'
42 | style.color = 'rgba(49,79,79,1)';
43 | style.radius = 5;
44 | style.selected = {
45 | color: 'rgba(255,255,0,0.5)',
46 | radius: 6
47 | };
48 | break;
49 | case 2: //'LineString'
50 | style.color = 'rgba(161,217,155,0.8)';
51 | style.size = 3;
52 | style.selected = {
53 | color: 'rgba(255,25,0,0.5)',
54 | size: 4
55 | };
56 | break;
57 | case 3: //'Polygon'
58 | style.color = fillColor;
59 | style.outline = {
60 | color: strokeColor,
61 | size: 1
62 | };
63 | style.selected = {
64 | color: 'rgba(255,140,0,0.3)',
65 | outline: {
66 | color: 'rgba(255,140,0,1)',
67 | size: 2
68 | }
69 | };
70 | break;
71 | }
72 |
73 | if (feature.layer.name === 'gaul_2014_adm1_label') {
74 | style.ajaxSource = function(mvtFeature) {
75 | var id = mvtFeature.id;
76 | return 'http://localhost:8888/fsp/2014/fsp/aggregations-no-name/' + id + '.json';
77 | };
78 |
79 | style.staticLabel = function(mvtFeature, ajaxData) {
80 | var style = {
81 | html: ajaxData.total_count,
82 | iconSize: [33,33],
83 | cssClass: 'label-icon-number',
84 | cssSelectedClass: 'label-icon-number-selected'
85 | };
86 | return style;
87 | };
88 | }
89 |
90 | return style;
91 | },
92 |
93 | /**
94 | * When we want to link events between layers, like clicking on a label and a
95 | * corresponding polygon freature, this will return the corresponding mapping
96 | * between layers. This provides knowledge of which other feature a given feature
97 | * is linked to.
98 | *
99 | * @param layerName the layer we want to know the linked layer from
100 | * @returns {string} returns corresponding linked layer
101 | */
102 | layerLink: function(layerName) {
103 | if (layerName.indexOf('_label') > -1) {
104 | return layerName.replace('_label','');
105 | }
106 | return layerName + '_label';
107 | }
108 |
109 | });
110 | debug.mvtSource = mvtSource;
111 |
112 | //Globals that we can change later.
113 | var fillColor = 'rgba(149,139,255,0.4)';
114 | var strokeColor = 'rgb(20,20,20)';
115 |
116 | //Add layer
117 | map.addLayer(mvtSource);
118 |
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-0.7.3/images/layers-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-0.7.3/images/layers-2x.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-0.7.3/images/layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-0.7.3/images/layers.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-0.7.3/images/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-0.7.3/images/marker-icon-2x.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-0.7.3/images/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-0.7.3/images/marker-icon.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-0.7.3/images/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-0.7.3/images/marker-shadow.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-0.7.3/leaflet.css:
--------------------------------------------------------------------------------
1 | /* required styles */
2 |
3 | .leaflet-map-pane,
4 | .leaflet-tile,
5 | .leaflet-marker-icon,
6 | .leaflet-marker-shadow,
7 | .leaflet-tile-pane,
8 | .leaflet-tile-container,
9 | .leaflet-overlay-pane,
10 | .leaflet-shadow-pane,
11 | .leaflet-marker-pane,
12 | .leaflet-popup-pane,
13 | .leaflet-overlay-pane svg,
14 | .leaflet-zoom-box,
15 | .leaflet-image-layer,
16 | .leaflet-layer {
17 | position: absolute;
18 | left: 0;
19 | top: 0;
20 | }
21 | .leaflet-container {
22 | overflow: hidden;
23 | -ms-touch-action: none;
24 | }
25 | .leaflet-tile,
26 | .leaflet-marker-icon,
27 | .leaflet-marker-shadow {
28 | -webkit-user-select: none;
29 | -moz-user-select: none;
30 | user-select: none;
31 | -webkit-user-drag: none;
32 | }
33 | .leaflet-marker-icon,
34 | .leaflet-marker-shadow {
35 | display: block;
36 | }
37 | /* map is broken in FF if you have max-width: 100% on tiles */
38 | .leaflet-container img {
39 | max-width: none !important;
40 | }
41 | /* stupid Android 2 doesn't understand "max-width: none" properly */
42 | .leaflet-container img.leaflet-image-layer {
43 | max-width: 15000px !important;
44 | }
45 | .leaflet-tile {
46 | filter: inherit;
47 | visibility: hidden;
48 | }
49 | .leaflet-tile-loaded {
50 | visibility: inherit;
51 | }
52 | .leaflet-zoom-box {
53 | width: 0;
54 | height: 0;
55 | }
56 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
57 | .leaflet-overlay-pane svg {
58 | -moz-user-select: none;
59 | }
60 |
61 | .leaflet-tile-pane { z-index: 2; }
62 | .leaflet-objects-pane { z-index: 3; }
63 | .leaflet-overlay-pane { z-index: 4; }
64 | .leaflet-shadow-pane { z-index: 5; }
65 | .leaflet-marker-pane { z-index: 6; }
66 | .leaflet-popup-pane { z-index: 7; }
67 |
68 | .leaflet-vml-shape {
69 | width: 1px;
70 | height: 1px;
71 | }
72 | .lvml {
73 | behavior: url(#default#VML);
74 | display: inline-block;
75 | position: absolute;
76 | }
77 |
78 |
79 | /* control positioning */
80 |
81 | .leaflet-control {
82 | position: relative;
83 | z-index: 7;
84 | pointer-events: auto;
85 | }
86 | .leaflet-top,
87 | .leaflet-bottom {
88 | position: absolute;
89 | z-index: 1000;
90 | pointer-events: none;
91 | }
92 | .leaflet-top {
93 | top: 0;
94 | }
95 | .leaflet-right {
96 | right: 0;
97 | }
98 | .leaflet-bottom {
99 | bottom: 0;
100 | }
101 | .leaflet-left {
102 | left: 0;
103 | }
104 | .leaflet-control {
105 | float: left;
106 | clear: both;
107 | }
108 | .leaflet-right .leaflet-control {
109 | float: right;
110 | }
111 | .leaflet-top .leaflet-control {
112 | margin-top: 10px;
113 | }
114 | .leaflet-bottom .leaflet-control {
115 | margin-bottom: 10px;
116 | }
117 | .leaflet-left .leaflet-control {
118 | margin-left: 10px;
119 | }
120 | .leaflet-right .leaflet-control {
121 | margin-right: 10px;
122 | }
123 |
124 |
125 | /* zoom and fade animations */
126 |
127 | .leaflet-fade-anim .leaflet-tile,
128 | .leaflet-fade-anim .leaflet-popup {
129 | opacity: 0;
130 | -webkit-transition: opacity 0.2s linear;
131 | -moz-transition: opacity 0.2s linear;
132 | -o-transition: opacity 0.2s linear;
133 | transition: opacity 0.2s linear;
134 | }
135 | .leaflet-fade-anim .leaflet-tile-loaded,
136 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
137 | opacity: 1;
138 | }
139 |
140 | .leaflet-zoom-anim .leaflet-zoom-animated {
141 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
142 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
143 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
144 | transition: transform 0.25s cubic-bezier(0,0,0.25,1);
145 | }
146 | .leaflet-zoom-anim .leaflet-tile,
147 | .leaflet-pan-anim .leaflet-tile,
148 | .leaflet-touching .leaflet-zoom-animated {
149 | -webkit-transition: none;
150 | -moz-transition: none;
151 | -o-transition: none;
152 | transition: none;
153 | }
154 |
155 | .leaflet-zoom-anim .leaflet-zoom-hide {
156 | visibility: hidden;
157 | }
158 |
159 |
160 | /* cursors */
161 |
162 | .leaflet-clickable {
163 | cursor: pointer;
164 | }
165 | .leaflet-container {
166 | cursor: -webkit-grab;
167 | cursor: -moz-grab;
168 | }
169 | .leaflet-popup-pane,
170 | .leaflet-control {
171 | cursor: auto;
172 | }
173 | .leaflet-dragging .leaflet-container,
174 | .leaflet-dragging .leaflet-clickable {
175 | cursor: move;
176 | cursor: -webkit-grabbing;
177 | cursor: -moz-grabbing;
178 | }
179 |
180 |
181 | /* visual tweaks */
182 |
183 | .leaflet-container {
184 | background: #ddd;
185 | outline: 0;
186 | }
187 | .leaflet-container a {
188 | color: #0078A8;
189 | }
190 | .leaflet-container a.leaflet-active {
191 | outline: 2px solid orange;
192 | }
193 | .leaflet-zoom-box {
194 | border: 2px dotted #38f;
195 | background: rgba(255,255,255,0.5);
196 | }
197 |
198 |
199 | /* general typography */
200 | .leaflet-container {
201 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
202 | }
203 |
204 |
205 | /* general toolbar styles */
206 |
207 | .leaflet-bar {
208 | box-shadow: 0 1px 5px rgba(0,0,0,0.65);
209 | border-radius: 4px;
210 | }
211 | .leaflet-bar a,
212 | .leaflet-bar a:hover {
213 | background-color: #fff;
214 | border-bottom: 1px solid #ccc;
215 | width: 26px;
216 | height: 26px;
217 | line-height: 26px;
218 | display: block;
219 | text-align: center;
220 | text-decoration: none;
221 | color: black;
222 | }
223 | .leaflet-bar a,
224 | .leaflet-control-layers-toggle {
225 | background-position: 50% 50%;
226 | background-repeat: no-repeat;
227 | display: block;
228 | }
229 | .leaflet-bar a:hover {
230 | background-color: #f4f4f4;
231 | }
232 | .leaflet-bar a:first-child {
233 | border-top-left-radius: 4px;
234 | border-top-right-radius: 4px;
235 | }
236 | .leaflet-bar a:last-child {
237 | border-bottom-left-radius: 4px;
238 | border-bottom-right-radius: 4px;
239 | border-bottom: none;
240 | }
241 | .leaflet-bar a.leaflet-disabled {
242 | cursor: default;
243 | background-color: #f4f4f4;
244 | color: #bbb;
245 | }
246 |
247 | .leaflet-touch .leaflet-bar a {
248 | width: 30px;
249 | height: 30px;
250 | line-height: 30px;
251 | }
252 |
253 |
254 | /* zoom control */
255 |
256 | .leaflet-control-zoom-in,
257 | .leaflet-control-zoom-out {
258 | font: bold 18px 'Lucida Console', Monaco, monospace;
259 | text-indent: 1px;
260 | }
261 | .leaflet-control-zoom-out {
262 | font-size: 20px;
263 | }
264 |
265 | .leaflet-touch .leaflet-control-zoom-in {
266 | font-size: 22px;
267 | }
268 | .leaflet-touch .leaflet-control-zoom-out {
269 | font-size: 24px;
270 | }
271 |
272 |
273 | /* layers control */
274 |
275 | .leaflet-control-layers {
276 | box-shadow: 0 1px 5px rgba(0,0,0,0.4);
277 | background: #fff;
278 | border-radius: 5px;
279 | }
280 | .leaflet-control-layers-toggle {
281 | background-image: url(images/layers.png);
282 | width: 36px;
283 | height: 36px;
284 | }
285 | .leaflet-retina .leaflet-control-layers-toggle {
286 | background-image: url(images/layers-2x.png);
287 | background-size: 26px 26px;
288 | }
289 | .leaflet-touch .leaflet-control-layers-toggle {
290 | width: 44px;
291 | height: 44px;
292 | }
293 | .leaflet-control-layers .leaflet-control-layers-list,
294 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle {
295 | display: none;
296 | }
297 | .leaflet-control-layers-expanded .leaflet-control-layers-list {
298 | display: block;
299 | position: relative;
300 | }
301 | .leaflet-control-layers-expanded {
302 | padding: 6px 10px 6px 6px;
303 | color: #333;
304 | background: #fff;
305 | }
306 | .leaflet-control-layers-selector {
307 | margin-top: 2px;
308 | position: relative;
309 | top: 1px;
310 | }
311 | .leaflet-control-layers label {
312 | display: block;
313 | }
314 | .leaflet-control-layers-separator {
315 | height: 0;
316 | border-top: 1px solid #ddd;
317 | margin: 5px -10px 5px -6px;
318 | }
319 |
320 |
321 | /* attribution and scale controls */
322 |
323 | .leaflet-container .leaflet-control-attribution {
324 | background: #fff;
325 | background: rgba(255, 255, 255, 0.7);
326 | margin: 0;
327 | }
328 | .leaflet-control-attribution,
329 | .leaflet-control-scale-line {
330 | padding: 0 5px;
331 | color: #333;
332 | }
333 | .leaflet-control-attribution a {
334 | text-decoration: none;
335 | }
336 | .leaflet-control-attribution a:hover {
337 | text-decoration: underline;
338 | }
339 | .leaflet-container .leaflet-control-attribution,
340 | .leaflet-container .leaflet-control-scale {
341 | font-size: 11px;
342 | }
343 | .leaflet-left .leaflet-control-scale {
344 | margin-left: 5px;
345 | }
346 | .leaflet-bottom .leaflet-control-scale {
347 | margin-bottom: 5px;
348 | }
349 | .leaflet-control-scale-line {
350 | border: 2px solid #777;
351 | border-top: none;
352 | line-height: 1.1;
353 | padding: 2px 5px 1px;
354 | font-size: 11px;
355 | white-space: nowrap;
356 | overflow: hidden;
357 | -moz-box-sizing: content-box;
358 | box-sizing: content-box;
359 |
360 | background: #fff;
361 | background: rgba(255, 255, 255, 0.5);
362 | }
363 | .leaflet-control-scale-line:not(:first-child) {
364 | border-top: 2px solid #777;
365 | border-bottom: none;
366 | margin-top: -2px;
367 | }
368 | .leaflet-control-scale-line:not(:first-child):not(:last-child) {
369 | border-bottom: 2px solid #777;
370 | }
371 |
372 | .leaflet-touch .leaflet-control-attribution,
373 | .leaflet-touch .leaflet-control-layers,
374 | .leaflet-touch .leaflet-bar {
375 | box-shadow: none;
376 | }
377 | .leaflet-touch .leaflet-control-layers,
378 | .leaflet-touch .leaflet-bar {
379 | border: 2px solid rgba(0,0,0,0.2);
380 | background-clip: padding-box;
381 | }
382 |
383 |
384 | /* popup */
385 |
386 | .leaflet-popup {
387 | position: absolute;
388 | text-align: center;
389 | }
390 | .leaflet-popup-content-wrapper {
391 | padding: 1px;
392 | text-align: left;
393 | border-radius: 12px;
394 | }
395 | .leaflet-popup-content {
396 | margin: 13px 19px;
397 | line-height: 1.4;
398 | }
399 | .leaflet-popup-content p {
400 | margin: 18px 0;
401 | }
402 | .leaflet-popup-tip-container {
403 | margin: 0 auto;
404 | width: 40px;
405 | height: 20px;
406 | position: relative;
407 | overflow: hidden;
408 | }
409 | .leaflet-popup-tip {
410 | width: 17px;
411 | height: 17px;
412 | padding: 1px;
413 |
414 | margin: -10px auto 0;
415 |
416 | -webkit-transform: rotate(45deg);
417 | -moz-transform: rotate(45deg);
418 | -ms-transform: rotate(45deg);
419 | -o-transform: rotate(45deg);
420 | transform: rotate(45deg);
421 | }
422 | .leaflet-popup-content-wrapper,
423 | .leaflet-popup-tip {
424 | background: white;
425 |
426 | box-shadow: 0 3px 14px rgba(0,0,0,0.4);
427 | }
428 | .leaflet-container a.leaflet-popup-close-button {
429 | position: absolute;
430 | top: 0;
431 | right: 0;
432 | padding: 4px 4px 0 0;
433 | text-align: center;
434 | width: 18px;
435 | height: 14px;
436 | font: 16px/14px Tahoma, Verdana, sans-serif;
437 | color: #c3c3c3;
438 | text-decoration: none;
439 | font-weight: bold;
440 | background: transparent;
441 | }
442 | .leaflet-container a.leaflet-popup-close-button:hover {
443 | color: #999;
444 | }
445 | .leaflet-popup-scrolled {
446 | overflow: auto;
447 | border-bottom: 1px solid #ddd;
448 | border-top: 1px solid #ddd;
449 | }
450 |
451 | .leaflet-oldie .leaflet-popup-content-wrapper {
452 | zoom: 1;
453 | }
454 | .leaflet-oldie .leaflet-popup-tip {
455 | width: 24px;
456 | margin: 0 auto;
457 |
458 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
459 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
460 | }
461 | .leaflet-oldie .leaflet-popup-tip-container {
462 | margin-top: -1px;
463 | }
464 |
465 | .leaflet-oldie .leaflet-control-zoom,
466 | .leaflet-oldie .leaflet-control-layers,
467 | .leaflet-oldie .leaflet-popup-content-wrapper,
468 | .leaflet-oldie .leaflet-popup-tip {
469 | border: 1px solid #999;
470 | }
471 |
472 |
473 | /* div icon */
474 |
475 | .leaflet-div-icon {
476 | background: #fff;
477 | border: 1px solid #666;
478 | }
479 |
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-master/images/layers-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-master/images/layers-2x.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-master/images/layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-master/images/layers.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-master/images/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-master/images/marker-icon-2x.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-master/images/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-master/images/marker-icon.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-master/images/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/lib/Leaflet/leaflet-master/images/marker-shadow.png
--------------------------------------------------------------------------------
/lib/Leaflet/leaflet-master/leaflet.css:
--------------------------------------------------------------------------------
1 | /* required styles */
2 |
3 | .leaflet-pane,
4 | .leaflet-tile,
5 | .leaflet-marker-icon,
6 | .leaflet-marker-shadow,
7 | .leaflet-tile-container,
8 | .leaflet-map-pane svg,
9 | .leaflet-map-pane canvas,
10 | .leaflet-zoom-box,
11 | .leaflet-image-layer,
12 | .leaflet-layer {
13 | position: absolute;
14 | left: 0;
15 | top: 0;
16 | }
17 | .leaflet-container {
18 | overflow: hidden;
19 | -ms-touch-action: none;
20 | }
21 | .leaflet-tile,
22 | .leaflet-marker-icon,
23 | .leaflet-marker-shadow {
24 | -webkit-user-select: none;
25 | -moz-user-select: none;
26 | user-select: none;
27 | -webkit-user-drag: none;
28 | }
29 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */
30 | .leaflet-safari .leaflet-tile {
31 | image-rendering: -webkit-optimize-contrast;
32 | }
33 | /* hack that prevents hw layers "stretching" when loading new tiles */
34 | .leaflet-safari .leaflet-tile-container {
35 | width: 1600px;
36 | height: 1600px;
37 | -webkit-transform-origin: 0 0;
38 | }
39 | .leaflet-marker-icon,
40 | .leaflet-marker-shadow {
41 | display: block;
42 | }
43 | /* map is broken in FF if you have max-width: 100% on tiles */
44 | .leaflet-container img {
45 | max-width: none !important;
46 | }
47 | /* stupid Android 2 doesn't understand "max-width: none" properly */
48 | .leaflet-container img.leaflet-image-layer {
49 | max-width: 15000px !important;
50 | }
51 | .leaflet-tile {
52 | filter: inherit;
53 | visibility: hidden;
54 | }
55 | .leaflet-tile-loaded {
56 | visibility: inherit;
57 | }
58 | .leaflet-zoom-box {
59 | width: 0;
60 | height: 0;
61 | -moz-box-sizing: border-box;
62 | box-sizing: border-box;
63 | z-index: 8;
64 | }
65 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
66 | .leaflet-overlay-pane svg {
67 | -moz-user-select: none;
68 | }
69 |
70 | .leaflet-pane { z-index: 4; }
71 |
72 | .leaflet-tile-pane { z-index: 2; }
73 | .leaflet-overlay-pane { z-index: 4; }
74 | .leaflet-shadow-pane { z-index: 5; }
75 | .leaflet-marker-pane { z-index: 6; }
76 | .leaflet-popup-pane { z-index: 7; }
77 |
78 | .leaflet-map-pane canvas { z-index: 1; }
79 | .leaflet-map-pane svg { z-index: 2; }
80 |
81 | .leaflet-vml-shape {
82 | width: 1px;
83 | height: 1px;
84 | }
85 | .lvml {
86 | behavior: url(#default#VML);
87 | display: inline-block;
88 | position: absolute;
89 | }
90 |
91 |
92 | /* control positioning */
93 |
94 | .leaflet-control {
95 | position: relative;
96 | z-index: 7;
97 | pointer-events: auto;
98 | }
99 | .leaflet-top,
100 | .leaflet-bottom {
101 | position: absolute;
102 | z-index: 1000;
103 | pointer-events: none;
104 | }
105 | .leaflet-top {
106 | top: 0;
107 | }
108 | .leaflet-right {
109 | right: 0;
110 | }
111 | .leaflet-bottom {
112 | bottom: 0;
113 | }
114 | .leaflet-left {
115 | left: 0;
116 | }
117 | .leaflet-control {
118 | float: left;
119 | clear: both;
120 | }
121 | .leaflet-right .leaflet-control {
122 | float: right;
123 | }
124 | .leaflet-top .leaflet-control {
125 | margin-top: 10px;
126 | }
127 | .leaflet-bottom .leaflet-control {
128 | margin-bottom: 10px;
129 | }
130 | .leaflet-left .leaflet-control {
131 | margin-left: 10px;
132 | }
133 | .leaflet-right .leaflet-control {
134 | margin-right: 10px;
135 | }
136 |
137 |
138 | /* zoom and fade animations */
139 |
140 | .leaflet-fade-anim .leaflet-tile,
141 | .leaflet-fade-anim .leaflet-popup {
142 | opacity: 0;
143 | -webkit-transition: opacity 0.2s linear;
144 | -moz-transition: opacity 0.2s linear;
145 | -o-transition: opacity 0.2s linear;
146 | transition: opacity 0.2s linear;
147 | }
148 | .leaflet-fade-anim .leaflet-tile-loaded,
149 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
150 | opacity: 1;
151 | }
152 |
153 | .leaflet-zoom-anim .leaflet-zoom-animated {
154 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
155 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
156 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
157 | transition: transform 0.25s cubic-bezier(0,0,0.25,1);
158 | }
159 | .leaflet-zoom-anim .leaflet-tile,
160 | .leaflet-pan-anim .leaflet-tile {
161 | -webkit-transition: none;
162 | -moz-transition: none;
163 | -o-transition: none;
164 | transition: none;
165 | }
166 |
167 | .leaflet-zoom-anim .leaflet-zoom-hide {
168 | visibility: hidden;
169 | }
170 |
171 |
172 | /* cursors */
173 |
174 | .leaflet-clickable {
175 | cursor: pointer;
176 | }
177 | .leaflet-container {
178 | cursor: -webkit-grab;
179 | cursor: -moz-grab;
180 | }
181 | .leaflet-crosshair,
182 | .leaflet-crosshair .leaflet-clickable {
183 | cursor: crosshair;
184 | }
185 | .leaflet-popup-pane,
186 | .leaflet-control {
187 | cursor: auto;
188 | }
189 | .leaflet-dragging .leaflet-container,
190 | .leaflet-dragging .leaflet-clickable {
191 | cursor: move;
192 | cursor: -webkit-grabbing;
193 | cursor: -moz-grabbing;
194 | }
195 |
196 |
197 | /* visual tweaks */
198 |
199 | .leaflet-container {
200 | background: #ddd;
201 | outline: 0;
202 | }
203 | .leaflet-container a {
204 | color: #0078A8;
205 | }
206 | .leaflet-container a.leaflet-active {
207 | outline: 2px solid orange;
208 | }
209 | .leaflet-zoom-box {
210 | border: 2px dotted #38f;
211 | background: rgba(255,255,255,0.5);
212 | }
213 |
214 |
215 | /* general typography */
216 | .leaflet-container {
217 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
218 | }
219 |
220 |
221 | /* general toolbar styles */
222 |
223 | .leaflet-bar {
224 | box-shadow: 0 1px 5px rgba(0,0,0,0.65);
225 | border-radius: 4px;
226 | }
227 | .leaflet-bar a,
228 | .leaflet-bar a:hover {
229 | background-color: #fff;
230 | border-bottom: 1px solid #ccc;
231 | width: 26px;
232 | height: 26px;
233 | line-height: 26px;
234 | display: block;
235 | text-align: center;
236 | text-decoration: none;
237 | color: black;
238 | }
239 | .leaflet-bar a,
240 | .leaflet-control-layers-toggle {
241 | background-position: 50% 50%;
242 | background-repeat: no-repeat;
243 | display: block;
244 | }
245 | .leaflet-bar a:hover {
246 | background-color: #f4f4f4;
247 | }
248 | .leaflet-bar a:first-child {
249 | border-top-left-radius: 4px;
250 | border-top-right-radius: 4px;
251 | }
252 | .leaflet-bar a:last-child {
253 | border-bottom-left-radius: 4px;
254 | border-bottom-right-radius: 4px;
255 | border-bottom: none;
256 | }
257 | .leaflet-bar a.leaflet-disabled {
258 | cursor: default;
259 | background-color: #f4f4f4;
260 | color: #bbb;
261 | }
262 |
263 | .leaflet-touch .leaflet-bar a {
264 | width: 30px;
265 | height: 30px;
266 | line-height: 30px;
267 | }
268 |
269 |
270 | /* zoom control */
271 |
272 | .leaflet-control-zoom-in,
273 | .leaflet-control-zoom-out {
274 | font: bold 18px 'Lucida Console', Monaco, monospace;
275 | text-indent: 1px;
276 | }
277 | .leaflet-control-zoom-out {
278 | font-size: 20px;
279 | }
280 |
281 | .leaflet-touch .leaflet-control-zoom-in {
282 | font-size: 22px;
283 | }
284 | .leaflet-touch .leaflet-control-zoom-out {
285 | font-size: 24px;
286 | }
287 |
288 |
289 | /* layers control */
290 |
291 | .leaflet-control-layers {
292 | box-shadow: 0 1px 5px rgba(0,0,0,0.4);
293 | background: #fff;
294 | border-radius: 5px;
295 | }
296 | .leaflet-control-layers-toggle {
297 | background-image: url(images/layers.png);
298 | width: 36px;
299 | height: 36px;
300 | }
301 | .leaflet-retina .leaflet-control-layers-toggle {
302 | background-image: url(images/layers-2x.png);
303 | background-size: 26px 26px;
304 | }
305 | .leaflet-touch .leaflet-control-layers-toggle {
306 | width: 44px;
307 | height: 44px;
308 | }
309 | .leaflet-control-layers .leaflet-control-layers-list,
310 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle {
311 | display: none;
312 | }
313 | .leaflet-control-layers-expanded .leaflet-control-layers-list {
314 | display: block;
315 | position: relative;
316 | }
317 | .leaflet-control-layers-expanded {
318 | padding: 6px 10px 6px 6px;
319 | color: #333;
320 | background: #fff;
321 | }
322 | .leaflet-control-layers-selector {
323 | margin-top: 2px;
324 | position: relative;
325 | top: 1px;
326 | }
327 | .leaflet-control-layers label {
328 | display: block;
329 | }
330 | .leaflet-control-layers-separator {
331 | height: 0;
332 | border-top: 1px solid #ddd;
333 | margin: 5px -10px 5px -6px;
334 | }
335 |
336 |
337 | /* attribution and scale controls */
338 |
339 | .leaflet-container .leaflet-control-attribution {
340 | background: #fff;
341 | background: rgba(255, 255, 255, 0.7);
342 | margin: 0;
343 | }
344 | .leaflet-control-attribution,
345 | .leaflet-control-scale-line {
346 | padding: 0 5px;
347 | color: #333;
348 | }
349 | .leaflet-control-attribution a {
350 | text-decoration: none;
351 | }
352 | .leaflet-control-attribution a:hover {
353 | text-decoration: underline;
354 | }
355 | .leaflet-container .leaflet-control-attribution,
356 | .leaflet-container .leaflet-control-scale {
357 | font-size: 11px;
358 | }
359 | .leaflet-left .leaflet-control-scale {
360 | margin-left: 5px;
361 | }
362 | .leaflet-bottom .leaflet-control-scale {
363 | margin-bottom: 5px;
364 | }
365 | .leaflet-control-scale-line {
366 | border: 2px solid #777;
367 | border-top: none;
368 | line-height: 1.1;
369 | padding: 2px 5px 1px;
370 | font-size: 11px;
371 | white-space: nowrap;
372 | overflow: hidden;
373 | -moz-box-sizing: content-box;
374 | box-sizing: content-box;
375 |
376 | background: #fff;
377 | background: rgba(255, 255, 255, 0.5);
378 | }
379 | .leaflet-control-scale-line:not(:first-child) {
380 | border-top: 2px solid #777;
381 | border-bottom: none;
382 | margin-top: -2px;
383 | }
384 | .leaflet-control-scale-line:not(:first-child):not(:last-child) {
385 | border-bottom: 2px solid #777;
386 | }
387 |
388 | .leaflet-touch .leaflet-control-attribution,
389 | .leaflet-touch .leaflet-control-layers,
390 | .leaflet-touch .leaflet-bar {
391 | box-shadow: none;
392 | }
393 | .leaflet-touch .leaflet-control-layers,
394 | .leaflet-touch .leaflet-bar {
395 | border: 2px solid rgba(0,0,0,0.2);
396 | background-clip: padding-box;
397 | }
398 |
399 |
400 | /* popup */
401 |
402 | .leaflet-popup {
403 | position: absolute;
404 | text-align: center;
405 | }
406 | .leaflet-popup-content-wrapper {
407 | padding: 1px;
408 | text-align: left;
409 | border-radius: 12px;
410 | }
411 | .leaflet-popup-content {
412 | margin: 13px 19px;
413 | line-height: 1.4;
414 | }
415 | .leaflet-popup-content p {
416 | margin: 18px 0;
417 | }
418 | .leaflet-popup-tip-container {
419 | margin: 0 auto;
420 | width: 40px;
421 | height: 20px;
422 | position: relative;
423 | overflow: hidden;
424 | }
425 | .leaflet-popup-tip {
426 | width: 17px;
427 | height: 17px;
428 | padding: 1px;
429 |
430 | margin: -10px auto 0;
431 |
432 | -webkit-transform: rotate(45deg);
433 | -moz-transform: rotate(45deg);
434 | -ms-transform: rotate(45deg);
435 | -o-transform: rotate(45deg);
436 | transform: rotate(45deg);
437 | }
438 | .leaflet-popup-content-wrapper,
439 | .leaflet-popup-tip {
440 | background: white;
441 |
442 | box-shadow: 0 3px 14px rgba(0,0,0,0.4);
443 | }
444 | .leaflet-container a.leaflet-popup-close-button {
445 | position: absolute;
446 | top: 0;
447 | right: 0;
448 | padding: 4px 4px 0 0;
449 | text-align: center;
450 | width: 18px;
451 | height: 14px;
452 | font: 16px/14px Tahoma, Verdana, sans-serif;
453 | color: #c3c3c3;
454 | text-decoration: none;
455 | font-weight: bold;
456 | background: transparent;
457 | }
458 | .leaflet-container a.leaflet-popup-close-button:hover {
459 | color: #999;
460 | }
461 | .leaflet-popup-scrolled {
462 | overflow: auto;
463 | border-bottom: 1px solid #ddd;
464 | border-top: 1px solid #ddd;
465 | }
466 |
467 | .leaflet-oldie .leaflet-popup-content-wrapper {
468 | zoom: 1;
469 | }
470 | .leaflet-oldie .leaflet-popup-tip {
471 | width: 24px;
472 | margin: 0 auto;
473 |
474 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
475 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
476 | }
477 | .leaflet-oldie .leaflet-popup-tip-container {
478 | margin-top: -1px;
479 | }
480 |
481 | .leaflet-oldie .leaflet-control-zoom,
482 | .leaflet-oldie .leaflet-control-layers,
483 | .leaflet-oldie .leaflet-popup-content-wrapper,
484 | .leaflet-oldie .leaflet-popup-tip {
485 | border: 1px solid #999;
486 | }
487 |
488 |
489 | /* div icon */
490 |
491 | .leaflet-div-icon {
492 | background: #fff;
493 | border: 1px solid #666;
494 | }
495 |
--------------------------------------------------------------------------------
/lib/jsts/javascript.util.js:
--------------------------------------------------------------------------------
1 | /*
2 | javascript.util is a port of selected parts of java.util to JavaScript which
3 | main purpose is to ease porting Java code to JavaScript.
4 |
5 | The MIT License (MIT)
6 |
7 | Copyright (C) 2011,2012 by The Authors
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in
17 | all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | THE SOFTWARE.
26 | */
27 | (function(c){var d,b,a,h,e,f={".js":[],".json":[],".css":[],".html":[]};h=function(a){a=Error("Could not find module '"+a+"'");a.code="MODULE_NOT_FOUND";return a};e=function(a,l,b){var e,h;if("function"===typeof a[l+b])return l+b;for(e=0;h=f[b][e];++e)if("function"===typeof a[l+h])return l+h;return null};d=function(a,l,k,c,f){var g,n;k=k.split("/");g=k.pop();if("."===g||".."===g)k.push(g),g="";for(;null!=(n=k.shift());)if(n&&"."!==n&&(".."===n?a=l.pop():(l.push(a),a=a[n]),!a))throw h(c);g&&"function"!==
28 | typeof a[g]&&((k=e(a,g,".js"))||(k=e(a,g,".json")),k||(k=e(a,g,".css")),k||(k=e(a,g,".html")),k?g=k:2!==f&&"object"===typeof a[g]&&(l.push(a),a=a[g],g=""));if(!g)return 1!==f&&a[":mainpath:"]?d(a,l,a[":mainpath:"],c,1):d(a,l,"index",c,2);f=a[g];if(!f)throw h(c);if(f.hasOwnProperty("module"))return f.module.exports;c={};f.module=g={exports:c};f.call(c,c,g,b(a,l));return g.exports};a=function(a,l,b){var e,f=b;e=b.charAt(0);var g=0;if("/"===e)f=f.slice(1),a=c["/"],l=[];else if("."!==e){e=f.split("/",
29 | 1)[0];a=c[e];if(!a)throw h(b);l=[];f=f.slice(e.length+1);f||((f=a[":mainpath:"])?g=1:(f="index",g=2))}return d(a,l,f,b,g)};b=function(b,l){return function(e){return a(b,[].concat(l),e)}};return b(c,[])})({"javascript.util":{lib:{"ArrayList.js":function(c,d,b){function a(a){this.array=[];a instanceof h&&this.addAll(a)}var h=b("./Collection");c=b("./List");var e=b("./OperationNotSupported"),f=b("./NoSuchElementException"),m=b("./IndexOutOfBoundsException");a.prototype=new c;a.prototype.array=null;a.prototype.add=
30 | function(a){this.array.push(a);return!0};a.prototype.addAll=function(a){for(a=a.iterator();a.hasNext();)this.add(a.next());return!0};a.prototype.set=function(a,b){var e=this.array[a];this.array[a]=b;return e};a.prototype.iterator=function(){return new a.Iterator(this)};a.prototype.get=function(a){if(0>a||a>=this.size())throw new m;return this.array[a]};a.prototype.isEmpty=function(){return 0===this.array.length};a.prototype.size=function(){return this.array.length};a.prototype.toArray=function(){for(var a=
31 | [],b=0,e=this.array.length;b dist/Leaflet.MapboxVectorTile.min.js",
56 | "test": "testling | faucet"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/DynamicLabel/DynamicLabel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Nicholas Hallahan
3 | * on 7/11/14.
4 | */
5 |
6 | // constants
7 | var POS_WKR_LOC = './scripts/lib/TileLayer.PBF.DynamicLabel/PositionWorker.js';
8 |
9 | // if using browserify
10 | if (typeof require === 'function') {
11 | var Feature = require('./Feature.js');
12 | }
13 |
14 | /**
15 | * There should be one instance of Label per MVTSource.
16 | *
17 | * @param map
18 | * @param options
19 | * @constructor
20 | */
21 | function DynamicLabel(map, pbfSource, options) {
22 | this.map = map;
23 | this.mvtSource = pbfSource;
24 |
25 | // default options
26 | this.options = {
27 | numPosWorkers: 8
28 | };
29 |
30 | // apply options
31 | for (var key in options) {
32 | this.options[key] = options[key];
33 | }
34 |
35 | this.positionWorkerPool = [];
36 | this.positionWorkerQueue = [];
37 |
38 | for (var wkr = 0; wkr < this.options.numPosWorkers; wkr++) {
39 | this.positionWorkerPool.push( new Worker(POS_WKR_LOC) );
40 | }
41 |
42 | /**
43 | * A hash containing keys to all of the tiles
44 | * currently in the view port.
45 | */
46 | this.activeTiles = {};
47 |
48 | this._determineActiveTiles();
49 | var self = this;
50 | this.map.on('move', function() {
51 | self._determineActiveTiles();
52 | });
53 |
54 | }
55 |
56 |
57 | /**
58 | * FACTORY METHODS
59 | */
60 |
61 | DynamicLabel.prototype.createFeature = function(pbfFeature, options) {
62 | return new Feature(this, pbfFeature, options);
63 | };
64 |
65 |
66 |
67 | /**
68 | * PRIVATE METHODS
69 | * @private
70 | */
71 |
72 | DynamicLabel.prototype._determineActiveTiles = function() {
73 | var activeTiles = this.activeTiles = {};
74 | var bounds = this.map.getPixelBounds();
75 | var tileSize = this.mvtSource.options.tileSize;
76 | var z = this.map.getZoom();
77 |
78 | var minX = Math.floor(bounds.min.x / tileSize);
79 | var minY = Math.floor(bounds.min.y / tileSize);
80 | var maxX = Math.floor(bounds.max.x / tileSize);
81 | var maxY = Math.floor(bounds.max.y / tileSize);
82 |
83 | for (var x = minX; x <= maxX; x++) {
84 | for (var y = minY; y <= maxY; y++) {
85 | activeTiles[z+':'+x+':'+y] = true;
86 | }
87 | }
88 | };
89 |
90 | DynamicLabel.prototype.submitPositionJob = function(job, onmessage) {
91 | var worker = this.positionWorkerPool.pop();
92 | if (!worker) {
93 | console.log('Enqueuing job for position worker...');
94 | this.positionWorkerQueue.push({job:job, onmessage:onmessage});
95 | } else {
96 | worker.onmessage = onmessage;
97 | worker.postMessage(job);
98 | }
99 | };
100 |
101 | DynamicLabel.prototype.freePositionWorker = function(worker) {
102 | if (this.positionWorkerQueue.length > 0) {
103 | var job = this.positionWorkerQueue.shift();
104 | worker.onmessage = job.onmessage;
105 | worker.postMessage(job.job);
106 | } else {
107 | this.positionWorkerPool.push(worker);
108 | }
109 | };
110 |
111 | /**
112 | * Exports this as a module if using Browserify.
113 | */
114 | if (typeof module !== 'undefined') {
115 | module.exports = DynamicLabel;
116 | }
117 |
--------------------------------------------------------------------------------
/src/DynamicLabel/Feature.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Nicholas Hallahan
3 | * on 7/11/14.
4 | */
5 |
6 | function Feature(label, pbfFeature, options) {
7 | this.dynamicLabel = label;
8 | this.mvtFeature = pbfFeature;
9 | this.mvtLayer = pbfFeature.mvtLayer;
10 | this.mvtSource = pbfFeature.mvtLayer.mvtSource;
11 | this.map = label.map;
12 | this.activeTiles = label.activeTiles;
13 | this.marker = null;
14 |
15 | this.tilePoints = {};
16 | this.tileLines = {};
17 | this.tilePolys = {};
18 |
19 | // default options
20 | this.options = {};
21 |
22 | // apply options
23 | for (var key in options) {
24 | this.options[key] = options[key];
25 | }
26 |
27 | // override the style function if specified
28 | if (pbfFeature.style.dynamicLabel) {
29 | this._styleFn = pbfFeature.style.dynamicLabel;
30 | }
31 |
32 | this.style = this._styleFn();
33 | this.icon = L.divIcon({
34 | className: this.style.cssClass || 'dynamicLabel-icon-text',
35 | html: this.style.html || 'No Label',
36 | iconSize: this.style.iconSize || [50,50]
37 | });
38 | }
39 |
40 | Feature.prototype.addTilePolys = function(ctx, polys) {
41 | this.tilePolys[ctx.id] = polys;
42 | this.computeLabelPosition();
43 | };
44 |
45 | Feature.prototype.computeLabelPosition = function() {
46 | var activeTiles = this.activeTiles;
47 | var tilePolys = {};
48 | // only passing over tiles currently on the screen
49 | for (var id in activeTiles) {
50 | var t = this.tilePolys[id];
51 | if (t) tilePolys[id] = t;
52 | }
53 | var dynamicLabel = this.dynamicLabel;
54 | var job = {
55 | extent: this.mvtFeature.extent,
56 | tileSize: this.mvtFeature.tileSize,
57 | tilePolys: tilePolys
58 | };
59 | var feature = this;
60 | this.dynamicLabel.submitPositionJob(job, function(evt) {
61 | var data = evt.data;
62 | console.log([data.status, data]);
63 | if (data.status !== 'WARN') {
64 | // returns worker to the pool
65 | dynamicLabel.freePositionWorker(this);
66 | }
67 |
68 | if (data.status === 'OK' && map.getZoom() === data.z) {
69 | var pt = L.point(data.x, data.y);
70 | positionMarker(feature, pt);
71 | }
72 |
73 | if (data.status === 'WARN' && data.tile === '5:18:16') {
74 | var cEdgeGeoJson = unprojectGeoJson(feature.map, data.cEdgePolys);
75 | var nEdgeGeoJson = unprojectGeoJson(feature.map, data.nEdgePolys);
76 |
77 | L.geoJson(cEdgeGeoJson, {
78 | style: {
79 | color: "#ff7800",
80 | weight: 3,
81 | opacity: 0.4,
82 | fill: false
83 | }
84 | }).addTo(feature.map);
85 |
86 | L.geoJson(nEdgeGeoJson, {
87 | style: {
88 | color: "green",
89 | weight: 1,
90 | opacity: 0.7,
91 | fill: false
92 | }
93 | }).addTo(feature.map);
94 | }
95 | });
96 | };
97 |
98 | function positionMarker(feature, pt) {
99 | var map = feature.map;
100 | var latLng = map.unproject(pt);
101 | if (!feature.marker) {
102 | feature.marker = L.marker(latLng, {icon: feature.icon});
103 | feature.marker.addTo(map);
104 | } else {
105 | feature.marker.setLatLng(latLng);
106 | }
107 | // L.marker(latLng).addTo(map);
108 | }
109 |
110 | /**
111 | * This is the default style function. This is overridden
112 | * if there is a style.dynamicLabel function in MVTFeature.
113 | */
114 | Feature.prototype._styleFn = function() {
115 |
116 | };
117 |
118 | /**
119 | * Converts projected GeoJSON back into WGS84 GeoJSON.
120 | * @param geojson
121 | * @returns {*}
122 | */
123 | function unprojectGeoJson(map, geojson) {
124 | var wgs84Coordinates = [];
125 | var wgs84GeoJson = {
126 | type: 'MultiPolygon',
127 | coordinates: wgs84Coordinates
128 | };
129 | var coords = geojson.coordinates;
130 | for (var i = 0, len = coords.length; i < len; i++) {
131 | var innerCoords = coords[i];
132 | wgs84Coordinates[i] = [];
133 | for (var j = 0, len2 = innerCoords.length; j < len2; j++) {
134 | var innerCoords2 = innerCoords[j];
135 | wgs84Coordinates[i][j] = [];
136 | for (var k = 0, len3 = innerCoords2.length; k < len3; k++) {
137 | var coord = innerCoords2[k];
138 | var latlng = map.unproject(L.point(coord));
139 | wgs84Coordinates[i][j][k] = [latlng.lng, latlng.lat];
140 | }
141 | }
142 | }
143 | return wgs84GeoJson;
144 | }
145 |
146 |
147 | /**
148 | * Exports this as a module if using Browserify.
149 | */
150 | if (typeof module !== 'undefined') {
151 | module.exports = Feature;
152 | }
153 |
--------------------------------------------------------------------------------
/src/DynamicLabel/PositionWorker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Nicholas Hallahan
3 | * on 7/11/14.
4 | */
5 |
6 | /**
7 | * Using the JavaScript Topology Suite to detect unions of polygons.
8 | */
9 | importScripts('../jsts/javascript.util.js', '../jsts/jsts-src.js');
10 |
11 | reader = new jsts.io.GeoJSONReader();
12 | parser = new jsts.io.GeoJSONParser();
13 |
14 | onmessage = function(evt) {
15 | var msg = evt.data;
16 | for (var key in msg) {
17 | var fn = calcPos[key];
18 | if (typeof fn === 'function') fn(msg);
19 | }
20 | };
21 |
22 | /**
23 | * Computes the position for a dynamicLabel depending on
24 | * the data type...
25 | */
26 | calcPos = {
27 | tilePoints: function(msg) { /* TODO */ },
28 | tileLines: function(msg) { /* TODO */ },
29 |
30 | tilePolys: function(msg) {
31 | var tilePolys = msg.tilePolys;
32 | var tileCls = classifyAndProjectTiles(tilePolys, msg.extent, msg.tileSize);
33 | var largestPoly = mergeAndFindLargestPolygon(tilePolys, tileCls);
34 | // if there are no polygons in the tiles, we dont have a center
35 | if (!largestPoly.tid) {
36 | postMessage({status: 'NO_POLY_IN_TILES'});
37 | } else {
38 | var center = centroid(tilePolys[largestPoly.tid][largestPoly.idx]);
39 | if (!center || !center.x || !center.y) {
40 | center = {
41 | status: 'ERR',
42 | description: 'No centroid calculated.'
43 | }
44 | } else {
45 | center.status = 'OK';
46 | center.z = parseInt(largestPoly.tid.split(':')[0]);
47 | }
48 | postMessage(center);
49 | }
50 | }
51 | };
52 |
53 | /**
54 | * Goes through each tile and and classifies the paths...
55 | *
56 | * @param tiles
57 | * @param extent
58 | */
59 | function classifyAndProjectTiles(tilePaths, extent, tileSize) {
60 | var tileCls = {};
61 | for (var id in tilePaths) {
62 | var t = tilePaths[id];
63 | var cls = classifyAndProject(t, extent, tileSize, id);
64 | tileCls[id] = cls;
65 | }
66 | return tileCls;
67 | }
68 |
69 | /**
70 | * Checks through the coordinate arrays and classifies if they
71 | * leave the bounds of the extent and where.
72 | *
73 | * @param coordsArray
74 | */
75 | function classifyAndProject(coordsArray, extent, tileSize, id) {
76 | // we need to know the tile address to project the coords
77 | var zxy = id.split(':');
78 | var x = parseInt(zxy[1]);
79 | var y = parseInt(zxy[2]);
80 |
81 | // the classification the the coords (what tile the intersect with)
82 | var cls = {
83 | internal: [],
84 | topLeft: [],
85 | left: [],
86 | bottomLeft: [],
87 | bottom: [],
88 | bottomRight: [],
89 | right: [],
90 | topRight: [],
91 | top: []
92 | };
93 |
94 | for (var i = 0, len = coordsArray.length; i < len; i++) {
95 | var coords = coordsArray[i];
96 | var overlapNeighbors = {};
97 | var pathInExtentAtLeastOnce = false;
98 |
99 | for (var j = 0, len2 = coords.length; j < len2; j++) {
100 | var coord = coords[j];
101 | var neighbor = checkCoordExtent(coord, extent);
102 |
103 | // project the coord to the pixel space of the world (Spherical Mercator for Zoom Level)
104 | coords[j] = project(coord, x, y, extent, tileSize);
105 |
106 | // coord is outside tile
107 | if (neighbor) {
108 | overlapNeighbors[neighbor] = true;
109 | }
110 | // coord is inside tile
111 | else {
112 | pathInExtentAtLeastOnce = true;
113 | }
114 | }
115 |
116 | // path is entirely inside of tile
117 | if (Object.keys(overlapNeighbors).length === 0) {
118 | cls.internal.push(i);
119 | }
120 | // path leaves the tile
121 | else {
122 | for (var neighbor in overlapNeighbors) {
123 | // We don't want paths that are never in the extent at all...
124 | if (pathInExtentAtLeastOnce) {
125 | cls[neighbor].push(i);
126 | }
127 | }
128 | }
129 | }
130 | return cls;
131 | }
132 |
133 | /**
134 | * Checks to see if the path has left the extent of the vector tile.
135 | * If so, we need to continue creating the polygon with coordinates
136 | * from a neighboring tile...
137 | *
138 | * @param pbfFeature
139 | * @param ctx
140 | * @param coords
141 | */
142 | function checkCoordExtent(coord, extent) {
143 | var x = coord.x;
144 | var y = coord.y;
145 |
146 | // outside left side
147 | if (x < 0) {
148 | // in top left tile
149 | if (y < 0) {
150 | return 'topLeft';
151 | }
152 | // in bottom left tile
153 | if (y > extent) {
154 | return 'bottomLeft';
155 | }
156 | // in left tile
157 | return 'left';
158 | }
159 |
160 | // outside right side
161 | if (x > extent) {
162 | // in top right tile
163 | if (y < 0) {
164 | return 'topRight';
165 | }
166 | // in bottom right tile
167 | if (y > extent) {
168 | return 'bottomRight';
169 | }
170 | // in right tile
171 | return 'right';
172 | }
173 |
174 | // outside top side
175 | if (y < 0) {
176 | return 'top';
177 | }
178 |
179 | // outside bottom side
180 | if (y > extent) {
181 | return 'bottom';
182 | }
183 |
184 | return null;
185 | }
186 |
187 | var neighborFns = {
188 | top: function(id) {
189 | var zxy = id.split(':');
190 | return zxy[0] + ':' + zxy[1] + ':' + (parseInt(zxy[2]) - 1);
191 | },
192 | topLeft: function(id) {
193 | var zxy = id.split(':');
194 | return zxy[0] + ':' + (parseInt(zxy[1]) - 1) + ':' + (parseInt(zxy[2]) - 1);
195 | },
196 | left: function(id) {
197 | var zxy = id.split(':');
198 | return zxy[0] + ':' + (parseInt(zxy[1]) - 1) + ':' + zxy[2];
199 | },
200 | bottomLeft: function(id) {
201 | var zxy = id.split(':');
202 | return zxy[0] + ':' + (parseInt(zxy[1]) - 1) + ':' + (parseInt(zxy[2]) + 1);
203 | },
204 | bottom: function(id) {
205 | var zxy = id.split(':');
206 | return zxy[0] + ':' + zxy[1] + ':' + (parseInt(zxy[2]) + 1);
207 | },
208 | bottomRight: function(id) {
209 | var zxy = id.split(':');
210 | return zxy[0] + ':' + (parseInt(zxy[1]) + 1) + ':' + (parseInt(zxy[2]) + 1);
211 | },
212 | right: function(id) {
213 | var zxy = id.split(':');
214 | return zxy[0] + ':' + (parseInt(zxy[1]) + 1) + ':' + zxy[2];
215 | },
216 | topRight: function(id) {
217 | var zxy = id.split(':');
218 | return zxy[0] + ':' + (parseInt(zxy[1]) + 1) + ':' + (parseInt(zxy[2]) - 1);
219 | }
220 | };
221 |
222 | /**
223 | * Projects a vector tile point to the Spherical Mercator pixel space for a given zoom level.
224 | *
225 | * @param vecPt
226 | * @param tileX
227 | * @param tileY
228 | * @param extent
229 | * @param tileSize
230 | */
231 | function project(vecPt, tileX, tileY, extent, tileSize) {
232 | var div = extent / tileSize;
233 | var xOffset = tileX * tileSize;
234 | var yOffset = tileY * tileSize;
235 | return {
236 | x: Math.floor(vecPt.x / div + xOffset),
237 | y: Math.floor(vecPt.y / div + yOffset)
238 | };
239 | }
240 |
241 | var inverseSides = {
242 | top: 'bottom',
243 | topLeft: 'bottomRight',
244 | left: 'right',
245 | bottomLeft: 'topRight',
246 | bottom: 'top',
247 | bottomRight: 'topLeft',
248 | right: 'left',
249 | topRight: 'bottomLeft'
250 | };
251 |
252 | function mergeAndFindLargestPolygon(tilePolys, tileCls) {
253 | var largestPoly = {
254 | tid: null, // tile id to get the tile in tilePolys
255 | idx: null, // index of array of polys in tile
256 | area: null // area of the poly
257 | };
258 | for (var id in tileCls) {
259 | var cCls = tileCls[id]; // center tile classifications
260 | var cPolys = tilePolys[id]; // center tile polygons
261 |
262 | // Polygons internal to a tile do not need to be merged, but they may be the largest...
263 | var internalClsArr = cCls.internal;
264 | if (typeof internalClsArr === 'array') {
265 | for (var i = 0, len = internalClsArr.length; i < len; i++) {
266 | findLargestPoly(largestPoly, tilePolys, id, internalClsArr[i]);
267 | }
268 | }
269 |
270 | for (var edge in neighborFns) {
271 | var cClsEdgeArr = cCls[edge]; // poly idxs for center edge classification
272 | // continue if there are no overlapping polys on a given edge...
273 | if (cClsEdgeArr.length === 0) {
274 | continue;
275 | }
276 | var nId = neighborFns[edge](id); // neighboring tile id
277 | var nCls = tileCls[nId]; // neighboring tile classifications
278 | var nPolys = tilePolys[nId]; // neighboring tile polygons
279 |
280 | for (var j = 0, len2 = cClsEdgeArr.length; j < len2; j++) {
281 | var cPolyIdx = cClsEdgeArr[j]; // a given center poly idx that overlaps the edge we are examining
282 |
283 | // If we have a neighboring tile, we get the overlapping polygons that correspond with the center tile.
284 | // We then try to union the polygons...
285 | if (nCls) {
286 | var inv = inverseSides[edge];
287 |
288 | // We are just going to do 1 poly from center tile at a time so we can keep track of the one we are actually doing the merge on...
289 | var cEdgePolys = polygonSetToGeoJson(cPolys, [cPolyIdx]); // 1 poly
290 | var nEdgePolys = polygonSetToGeoJson(nPolys, nCls[inv]); // 1 or more polys
291 |
292 | var ctrJsts = reader.read(cEdgePolys);
293 | var nbrJsts = reader.read(nEdgePolys);
294 |
295 | if (id === '5:18:16') {
296 | console.log('b');
297 | }
298 | try {
299 | // https://www.youtube.com/watch?v=RdSmokR0Enk
300 | var union = ctrJsts.union(nbrJsts);
301 |
302 | // the new merged polygon
303 | var unionPoly = union.shell.points;
304 |
305 | // Neighboring tile's inverse edge should be empty,
306 | // because the corresponding shape has been merged.
307 | nCls[inv] = [];
308 |
309 | // Replace the polygon in the center tile with the new merged polygon
310 | // so other tiles can merge this if needed.
311 | cPolys[cPolyIdx] = unionPoly;
312 |
313 | } catch (e) {
314 | //NH TODO: WHY?
315 | postMessage({
316 | status: 'WARN',
317 | details: 'union failed',
318 | ctrJsts:ctrJsts,
319 | nbrJsts:nbrJsts,
320 | cEdgePolys:cEdgePolys,
321 | nEdgePolys:nEdgePolys,
322 | e:e,
323 | tile: id
324 | });
325 | }
326 | }
327 |
328 | // Regardless of whether we unioned or not, we want to check to see if this polygon is the largest...
329 | findLargestPoly(largestPoly, tilePolys, id, cPolyIdx);
330 | }
331 |
332 | }
333 | }
334 | return largestPoly;
335 | }
336 |
337 | /**
338 | * Converts the array of arrays of {x,y} points to GeoJSON.
339 | *
340 | * Note that the GeoJSON we are using is in the projected pixel
341 | * space. Normally GeoJSON is WGS84, but we don't care, because
342 | * we are just doing a union topology check.
343 | *
344 | * @param polys
345 | * @returns {{type: string, coordinates: Array}}
346 | */
347 | function polygonSetToGeoJson(polys, idxArr) {
348 | var coordinates = [];
349 | var geojson = {
350 | type: 'MultiPolygon',
351 | coordinates: coordinates
352 | };
353 | for (var i = 0, len = idxArr.length; i < len; i++) {
354 | var idx = idxArr[i];
355 | var poly = polys[idx];
356 | var geoPoly = [];
357 | for (var j = 0, len2 = poly.length; j < len2; j++) {
358 | var pt = poly[j];
359 | var geoPt = [pt.x, pt.y];
360 | geoPoly.push(geoPt);
361 | }
362 | geoPoly = [geoPoly]; // its an array in array, other items in the array would be rings
363 | coordinates.push(geoPoly);
364 | }
365 | return geojson;
366 | }
367 |
368 |
369 | // http://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
370 |
371 | function area(poly) {
372 | var area = 0;
373 | var len = poly.length;
374 | for (var i = 0, j = len - 1; i < len; j=i, i++) {
375 | var p1 = poly[j];
376 | var p2 = poly[i];
377 |
378 | area += p1.x * p2.y - p2.x * p1.y;
379 | }
380 |
381 | return Math.abs(area / 2);
382 | }
383 |
384 | /*
385 | NH TODO: We are indeed getting the centroid, but ideally we
386 | want to check if the centroid is actually within the polygon
387 | for the polygons that bend like a boomarang. If it is outside,
388 | we need to nudge it over until it is inside...
389 | */
390 | function centroid(poly) {
391 | var len = poly.length;
392 | var x = 0;
393 | var y = 0;
394 | for (var i = 0, j = len - 1; i < len; j=i, i++) {
395 | var p1 = poly[j];
396 | var p2 = poly[i];
397 | var f = p1.x * p2.y - p2.x * p1.y;
398 | x += (p1.x + p2.x) * f;
399 | y += (p1.y + p2.y) * f;
400 | }
401 | f = area(poly) * 6;
402 |
403 | return {
404 | x: Math.abs(x/f),
405 | y: Math.abs(y/f)
406 | };
407 | }
408 |
409 | function findLargestPoly(largestPoly, tilePolys, tid, idx) {
410 | if (!largestPoly.tid) {
411 | largestPoly.tid = tid;
412 | largestPoly.idx = idx;
413 | largestPoly.area = area(tilePolys[tid][idx]);
414 | return largestPoly;
415 | }
416 | var largestArea = largestPoly.area;
417 | var polyArea = area(tilePolys[tid][idx]);
418 |
419 | if (polyArea > largestArea) {
420 | largestPoly.tid = tid;
421 | largestPoly.idx = idx;
422 | largestPoly.area = polyArea;
423 | }
424 |
425 | return largestPoly;
426 | }
427 |
--------------------------------------------------------------------------------
/src/DynamicLabel/label.css:
--------------------------------------------------------------------------------
1 | .label-icon-number {
2 | background-color: #6eafff;
3 | display: inline-block;
4 | cursor: pointer;
5 | color: #ffffff;
6 | font-size: 18px;
7 | border-radius: 50%;
8 | text-align: center;
9 | vertical-align: middle;
10 | line-height: 35px;
11 | box-shadow: 0px 0px 0px 6px rgba(255,255,255,0.7);
12 | width: 35px;
13 | height: 35px;
14 | }
15 |
16 | .label-icon-text {
17 | display: inline-block;
18 | cursor: pointer;
19 | font-size: 18px;
20 | text-align: center;
21 | vertical-align: middle;
22 | text-shadow: 0px 1px 1px #232323;
23 | color: #ebf9f5;
24 | }
--------------------------------------------------------------------------------
/src/MVTFeature.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Ryan Whitley, Daniel Duarte, and Nicholas Hallahan
3 | * on 6/03/14.
4 | */
5 | var Util = require('./MVTUtil');
6 | var StaticLabel = require('./StaticLabel/StaticLabel.js');
7 |
8 | module.exports = MVTFeature;
9 |
10 | function MVTFeature(mvtLayer, vtf, ctx, id, style) {
11 | if (!vtf) return null;
12 |
13 | // Apply all of the properties of vtf to this object.
14 | for (var key in vtf) {
15 | this[key] = vtf[key];
16 | }
17 |
18 | this.mvtLayer = mvtLayer;
19 | this.mvtSource = mvtLayer.mvtSource;
20 | this.map = mvtLayer.mvtSource.map;
21 |
22 | this.id = id;
23 |
24 | this.layerLink = this.mvtSource.layerLink;
25 | this.toggleEnabled = true;
26 | this.selected = false;
27 |
28 | // how much we divide the coordinate from the vector tile
29 | this.divisor = vtf.extent / ctx.tileSize;
30 | this.extent = vtf.extent;
31 | this.tileSize = ctx.tileSize;
32 |
33 | //An object to store the paths and contexts for this feature
34 | this.tiles = {};
35 |
36 | this.style = style;
37 |
38 | //Add to the collection
39 | this.addTileFeature(vtf, ctx);
40 |
41 | var self = this;
42 | this.map.on('zoomend', function() {
43 | self.staticLabel = null;
44 | });
45 |
46 | if (style && style.dynamicLabel && typeof style.dynamicLabel === 'function') {
47 | this.dynamicLabel = this.mvtSource.dynamicLabel.createFeature(this);
48 | }
49 |
50 | ajax(self);
51 | }
52 |
53 |
54 | function ajax(self) {
55 | var style = self.style;
56 | if (style && style.ajaxSource && typeof style.ajaxSource === 'function') {
57 | var ajaxEndpoint = style.ajaxSource(self);
58 | if (ajaxEndpoint) {
59 | Util.getJSON(ajaxEndpoint, function(error, response, body) {
60 | if (error) {
61 | throw ['ajaxSource AJAX Error', error];
62 | } else {
63 | ajaxCallback(self, response);
64 | return true;
65 | }
66 | });
67 | }
68 | }
69 | return false;
70 | }
71 |
72 | function ajaxCallback(self, response) {
73 | self.ajaxData = response;
74 |
75 | /**
76 | * You can attach a callback function to a feature in your app
77 | * that will get called whenever new ajaxData comes in. This
78 | * can be used to update UI that looks at data from within a feature.
79 | *
80 | * setStyle may possibly have a style with a different ajaxData source,
81 | * and you would potentially get new contextual data for your feature.
82 | *
83 | * TODO: This needs to be documented.
84 | */
85 | if (typeof self.ajaxDataReceived === 'function') {
86 | self.ajaxDataReceived(self, response);
87 | }
88 |
89 | self._setStyle(self.mvtLayer.style);
90 | redrawTiles(self);
91 | }
92 |
93 | MVTFeature.prototype._setStyle = function(styleFn) {
94 | this.style = styleFn(this, this.ajaxData);
95 |
96 | // The label gets removed, and the (re)draw,
97 | // that is initiated by the MVTLayer creates a new label.
98 | this.removeLabel();
99 | };
100 |
101 | MVTFeature.prototype.setStyle = function(styleFn) {
102 | this.ajaxData = null;
103 | this.style = styleFn(this, null);
104 | var hasAjaxSource = ajax(this);
105 | if (!hasAjaxSource) {
106 | // The label gets removed, and the (re)draw,
107 | // that is initiated by the MVTLayer creates a new label.
108 | this.removeLabel();
109 | }
110 | };
111 |
112 | MVTFeature.prototype.draw = function(canvasID) {
113 | //Get the info from the tiles list
114 | var tileInfo = this.tiles[canvasID];
115 |
116 | var vtf = tileInfo.vtf;
117 | var ctx = tileInfo.ctx;
118 |
119 | //Get the actual canvas from the parent layer's _tiles object.
120 | var xy = canvasID.split(":").slice(1, 3).join(":");
121 | ctx.canvas = this.mvtLayer._tiles[xy];
122 |
123 | // This could be used to directly compute the style function from the layer on every draw.
124 | // This is much less efficient...
125 | // this.style = this.mvtLayer.style(this);
126 |
127 | if (this.selected) {
128 | var style = this.style.selected || this.style;
129 | } else {
130 | var style = this.style;
131 | }
132 |
133 | switch (vtf.type) {
134 | case 1: //Point
135 | this._drawPoint(ctx, vtf.coordinates, style);
136 | if (!this.staticLabel && typeof this.style.staticLabel === 'function') {
137 | if (this.style.ajaxSource && !this.ajaxData) {
138 | break;
139 | }
140 | this._drawStaticLabel(ctx, vtf.coordinates, style);
141 | }
142 | break;
143 |
144 | case 2: //LineString
145 | this._drawLineString(ctx, vtf.coordinates, style);
146 | break;
147 |
148 | case 3: //Polygon
149 | this._drawPolygon(ctx, vtf.coordinates, style);
150 | break;
151 |
152 | default:
153 | throw new Error('Unmanaged type: ' + vtf.type);
154 | }
155 |
156 | };
157 |
158 | MVTFeature.prototype.getPathsForTile = function(canvasID) {
159 | //Get the info from the parts list
160 | return this.tiles[canvasID].paths;
161 | };
162 |
163 | MVTFeature.prototype.addTileFeature = function(vtf, ctx) {
164 | //Store the important items in the tiles list
165 |
166 | //We only want to store info for tiles for the current map zoom. If it is tile info for another zoom level, ignore it
167 | //Also, if there are existing tiles in the list for other zoom levels, expunge them.
168 | var zoom = this.map.getZoom();
169 |
170 | if(ctx.zoom != zoom) return;
171 |
172 | this.clearTileFeatures(zoom); //TODO: This iterates thru all tiles every time a new tile is added. Figure out a better way to do this.
173 |
174 | this.tiles[ctx.id] = {
175 | ctx: ctx,
176 | vtf: vtf,
177 | paths: []
178 | };
179 |
180 | };
181 |
182 |
183 | /**
184 | * Clear the inner list of tile features if they don't match the given zoom.
185 | *
186 | * @param zoom
187 | */
188 | MVTFeature.prototype.clearTileFeatures = function(zoom) {
189 | //If stored tiles exist for other zoom levels, expunge them from the list.
190 | for (var key in this.tiles) {
191 | if(key.split(":")[0] != zoom) delete this.tiles[key];
192 | }
193 | };
194 |
195 | /**
196 | * Redraws all of the tiles associated with a feature. Useful for
197 | * style change and toggling.
198 | *
199 | * @param self
200 | */
201 | function redrawTiles(self) {
202 | //Redraw the whole tile, not just this vtf
203 | var tiles = self.tiles;
204 | var mvtLayer = self.mvtLayer;
205 |
206 | for (var id in tiles) {
207 | var tileZoom = parseInt(id.split(':')[0]);
208 | var mapZoom = self.map.getZoom();
209 | if (tileZoom === mapZoom) {
210 | //Redraw the tile
211 | mvtLayer.redrawTile(id);
212 | }
213 | }
214 | }
215 |
216 | MVTFeature.prototype.toggle = function() {
217 | if (this.selected) {
218 | this.deselect();
219 | } else {
220 | this.select();
221 | }
222 | };
223 |
224 | MVTFeature.prototype.select = function() {
225 | this.selected = true;
226 | this.mvtSource.featureSelected(this);
227 | redrawTiles(this);
228 | var linkedFeature = this.linkedFeature();
229 | if (linkedFeature && linkedFeature.staticLabel && !linkedFeature.staticLabel.selected) {
230 | linkedFeature.staticLabel.select();
231 | }
232 | };
233 |
234 | MVTFeature.prototype.deselect = function() {
235 | this.selected = false;
236 | this.mvtSource.featureDeselected(this);
237 | redrawTiles(this);
238 | var linkedFeature = this.linkedFeature();
239 | if (linkedFeature && linkedFeature.staticLabel && linkedFeature.staticLabel.selected) {
240 | linkedFeature.staticLabel.deselect();
241 | }
242 | };
243 |
244 | MVTFeature.prototype.on = function(eventType, callback) {
245 | this._eventHandlers[eventType] = callback;
246 | };
247 |
248 | MVTFeature.prototype._drawPoint = function(ctx, coordsArray, style) {
249 | if (!style) return;
250 | if (!ctx || !ctx.canvas) return;
251 |
252 | var tile = this.tiles[ctx.id];
253 |
254 | //Get radius
255 | var radius = 1;
256 | if (typeof style.radius === 'function') {
257 | radius = style.radius(ctx.zoom); //Allows for scale dependent rednering
258 | }
259 | else{
260 | radius = style.radius;
261 | }
262 |
263 | var p = this._tilePoint(coordsArray[0][0]);
264 | var c = ctx.canvas;
265 | var ctx2d;
266 | try{
267 | ctx2d = c.getContext('2d');
268 | }
269 | catch(e){
270 | console.log("_drawPoint error: " + e);
271 | return;
272 | }
273 |
274 | ctx2d.beginPath();
275 | ctx2d.fillStyle = style.color;
276 | ctx2d.arc(p.x, p.y, radius, 0, Math.PI * 2);
277 | ctx2d.closePath();
278 | ctx2d.fill();
279 |
280 | if(style.lineWidth && style.strokeStyle){
281 | ctx2d.lineWidth = style.lineWidth;
282 | ctx2d.strokeStyle = style.strokeStyle;
283 | ctx2d.stroke();
284 | }
285 |
286 | ctx2d.restore();
287 | tile.paths.push([p]);
288 | };
289 |
290 | MVTFeature.prototype._drawLineString = function(ctx, coordsArray, style) {
291 | if (!style) return;
292 | if (!ctx || !ctx.canvas) return;
293 |
294 | var ctx2d = ctx.canvas.getContext('2d');
295 | ctx2d.strokeStyle = style.color;
296 | ctx2d.lineWidth = style.size;
297 | ctx2d.beginPath();
298 |
299 | var projCoords = [];
300 | var tile = this.tiles[ctx.id];
301 |
302 | for (var gidx in coordsArray) {
303 | var coords = coordsArray[gidx];
304 |
305 | for (i = 0; i < coords.length; i++) {
306 | var method = (i === 0 ? 'move' : 'line') + 'To';
307 | var proj = this._tilePoint(coords[i]);
308 | projCoords.push(proj);
309 | ctx2d[method](proj.x, proj.y);
310 | }
311 | }
312 |
313 | ctx2d.stroke();
314 | ctx2d.restore();
315 |
316 | tile.paths.push(projCoords);
317 | };
318 |
319 | MVTFeature.prototype._drawPolygon = function(ctx, coordsArray, style) {
320 | if (!style) return;
321 | if (!ctx || !ctx.canvas) return;
322 |
323 | var ctx2d = ctx.canvas.getContext('2d');
324 | var outline = style.outline;
325 |
326 | // color may be defined via function to make choropleth work right
327 | if (typeof style.color === 'function') {
328 | ctx2d.fillStyle = style.color(ctx2d);
329 | } else {
330 | ctx2d.fillStyle = style.color;
331 | }
332 |
333 | if (outline) {
334 | ctx2d.strokeStyle = outline.color;
335 | ctx2d.lineWidth = outline.size;
336 | }
337 | ctx2d.beginPath();
338 |
339 | var projCoords = [];
340 | var tile = this.tiles[ctx.id];
341 |
342 | var featureLabel = this.dynamicLabel;
343 | if (featureLabel) {
344 | featureLabel.addTilePolys(ctx, coordsArray);
345 | }
346 |
347 | for (var gidx = 0, len = coordsArray.length; gidx < len; gidx++) {
348 | var coords = coordsArray[gidx];
349 |
350 | for (var i = 0; i < coords.length; i++) {
351 | var coord = coords[i];
352 | var method = (i === 0 ? 'move' : 'line') + 'To';
353 | var proj = this._tilePoint(coords[i]);
354 | projCoords.push(proj);
355 | ctx2d[method](proj.x, proj.y);
356 | }
357 | }
358 |
359 | ctx2d.closePath();
360 | ctx2d.fill();
361 | if (outline) {
362 | ctx2d.stroke();
363 | }
364 |
365 | tile.paths.push(projCoords);
366 |
367 | };
368 |
369 | MVTFeature.prototype._drawStaticLabel = function(ctx, coordsArray, style) {
370 | if (!style) return;
371 | if (!ctx) return;
372 |
373 | // If the corresponding layer is not on the map,
374 | // we dont want to put on a label.
375 | if (!this.mvtLayer._map) return;
376 |
377 | var vecPt = this._tilePoint(coordsArray[0][0]);
378 |
379 | // We're making a standard Leaflet Marker for this label.
380 | var p = this._project(vecPt, ctx.tile.x, ctx.tile.y, this.extent, this.tileSize); //vectile pt to merc pt
381 | var mercPt = L.point(p.x, p.y); // make into leaflet obj
382 | var latLng = this.map.unproject(mercPt); // merc pt to latlng
383 |
384 | this.staticLabel = new StaticLabel(this, ctx, latLng, style);
385 | this.mvtLayer.featureWithLabelAdded(this);
386 | };
387 |
388 | MVTFeature.prototype.removeLabel = function() {
389 | if (!this.staticLabel) return;
390 | this.staticLabel.remove();
391 | this.staticLabel = null;
392 | };
393 |
394 | /**
395 | * Projects a vector tile point to the Spherical Mercator pixel space for a given zoom level.
396 | *
397 | * @param vecPt
398 | * @param tileX
399 | * @param tileY
400 | * @param extent
401 | * @param tileSize
402 | */
403 | MVTFeature.prototype._project = function(vecPt, tileX, tileY, extent, tileSize) {
404 | var xOffset = tileX * tileSize;
405 | var yOffset = tileY * tileSize;
406 | return {
407 | x: Math.floor(vecPt.x + xOffset),
408 | y: Math.floor(vecPt.y + yOffset)
409 | };
410 | };
411 |
412 | /**
413 | * Takes a coordinate from a vector tile and turns it into a Leaflet Point.
414 | *
415 | * @param ctx
416 | * @param coords
417 | * @returns {eGeomType.Point}
418 | * @private
419 | */
420 | MVTFeature.prototype._tilePoint = function(coords) {
421 | return new L.Point(coords.x / this.divisor, coords.y / this.divisor);
422 | };
423 |
424 | MVTFeature.prototype.linkedFeature = function() {
425 | var linkedLayer = this.mvtLayer.linkedLayer();
426 | if(linkedLayer){
427 | var linkedFeature = linkedLayer.features[this.id];
428 | return linkedFeature;
429 | }else{
430 | return null;
431 | }
432 | };
433 |
434 |
--------------------------------------------------------------------------------
/src/MVTLayer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Ryan Whitley on 5/17/14.
3 | */
4 | /** Forked from https://gist.github.com/DGuidi/1716010 **/
5 | var MVTFeature = require('./MVTFeature');
6 | var Util = require('./MVTUtil');
7 |
8 | module.exports = L.TileLayer.Canvas.extend({
9 |
10 | options: {
11 | debug: false,
12 | isHiddenLayer: false,
13 | getIDForLayerFeature: function() {},
14 | tileSize: 256,
15 | lineClickTolerance: 2
16 | },
17 |
18 | _featureIsClicked: {},
19 |
20 | _isPointInPoly: function(pt, poly) {
21 | if(poly && poly.length) {
22 | for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
23 | ((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
24 | && (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
25 | && (c = !c);
26 | return c;
27 | }
28 | },
29 |
30 | _getDistanceFromLine: function(pt, pts) {
31 | var min = Number.POSITIVE_INFINITY;
32 | if (pts && pts.length > 1) {
33 | pt = L.point(pt.x, pt.y);
34 | for (var i = 0, l = pts.length - 1; i < l; i++) {
35 | var test = this._projectPointOnLineSegment(pt, pts[i], pts[i + 1]);
36 | if (test.distance <= min) {
37 | min = test.distance;
38 | }
39 | }
40 | }
41 | return min;
42 | },
43 |
44 | _projectPointOnLineSegment: function(p, r0, r1) {
45 | var lineLength = r0.distanceTo(r1);
46 | if (lineLength < 1) {
47 | return {distance: p.distanceTo(r0), coordinate: r0};
48 | }
49 | var u = ((p.x - r0.x) * (r1.x - r0.x) + (p.y - r0.y) * (r1.y - r0.y)) / Math.pow(lineLength, 2);
50 | if (u < 0.0000001) {
51 | return {distance: p.distanceTo(r0), coordinate: r0};
52 | }
53 | if (u > 0.9999999) {
54 | return {distance: p.distanceTo(r1), coordinate: r1};
55 | }
56 | var a = L.point(r0.x + u * (r1.x - r0.x), r0.y + u * (r1.y - r0.y));
57 | return {distance: p.distanceTo(a), point: a};
58 | },
59 |
60 | initialize: function(mvtSource, options) {
61 | var self = this;
62 | self.mvtSource = mvtSource;
63 | L.Util.setOptions(this, options);
64 |
65 | this.style = options.style;
66 | this.name = options.name;
67 | this._canvasIDToFeatures = {};
68 | this.features = {};
69 | this.featuresWithLabels = [];
70 | this._highestCount = 0;
71 | },
72 |
73 | onAdd: function(map) {
74 | var self = this;
75 | self.map = map;
76 | L.TileLayer.Canvas.prototype.onAdd.call(this, map);
77 | map.on('layerremove', function(e) {
78 | // we only want to do stuff when the layerremove event is on this layer
79 | if (e.layer._leaflet_id === self._leaflet_id) {
80 | removeLabels(self);
81 | }
82 | });
83 | },
84 |
85 | drawTile: function(canvas, tilePoint, zoom) {
86 |
87 | var ctx = {
88 | canvas: canvas,
89 | tile: tilePoint,
90 | zoom: zoom,
91 | tileSize: this.options.tileSize
92 | };
93 |
94 | ctx.id = Util.getContextID(ctx);
95 |
96 | if (!this._canvasIDToFeatures[ctx.id]) {
97 | this._initializeFeaturesHash(ctx);
98 | }
99 | if (!this.features) {
100 | this.features = {};
101 | }
102 |
103 | },
104 |
105 | _initializeFeaturesHash: function(ctx){
106 | this._canvasIDToFeatures[ctx.id] = {};
107 | this._canvasIDToFeatures[ctx.id].features = [];
108 | this._canvasIDToFeatures[ctx.id].canvas = ctx.canvas;
109 | },
110 |
111 | _draw: function(ctx) {
112 | //Draw is handled by the parent MVTSource object
113 | },
114 | getCanvas: function(parentCtx){
115 | //This gets called if a vector tile feature has already been parsed.
116 | //We've already got the geom, just get on with the drawing.
117 | //Need a way to pluck a canvas element from this layer given the parent layer's id.
118 | //Wait for it to get loaded before proceeding.
119 | var tilePoint = parentCtx.tile;
120 | var ctx = this._tiles[tilePoint.x + ":" + tilePoint.y];
121 |
122 | if(ctx){
123 | parentCtx.canvas = ctx;
124 | this.redrawTile(parentCtx.id);
125 | return;
126 | }
127 |
128 | var self = this;
129 |
130 | //This is a timer that will wait for a criterion to return true.
131 | //If not true within the timeout duration, it will move on.
132 | waitFor(function () {
133 | ctx = self._tiles[tilePoint.x + ":" + tilePoint.y];
134 | if(ctx) {
135 | return true;
136 | }
137 | },
138 | function(){
139 | //When it finishes, do this.
140 | ctx = self._tiles[tilePoint.x + ":" + tilePoint.y];
141 | parentCtx.canvas = ctx;
142 | self.redrawTile(parentCtx.id);
143 |
144 | }, //when done, go to next flow
145 | 2000); //The Timeout milliseconds. After this, give up and move on
146 |
147 | },
148 |
149 | parseVectorTileLayer: function(vtl, ctx) {
150 | var self = this;
151 | var tilePoint = ctx.tile;
152 | var layerCtx = { canvas: null, id: ctx.id, tile: ctx.tile, zoom: ctx.zoom, tileSize: ctx.tileSize};
153 |
154 | //See if we can pluck the child tile from this PBF tile layer based on the master layer's tile id.
155 | layerCtx.canvas = self._tiles[tilePoint.x + ":" + tilePoint.y];
156 |
157 |
158 |
159 | //Initialize this tile's feature storage hash, if it hasn't already been created. Used for when filters are updated, and features are cleared to prepare for a fresh redraw.
160 | if (!this._canvasIDToFeatures[layerCtx.id]) {
161 | this._initializeFeaturesHash(layerCtx);
162 | }else{
163 | //Clear this tile's previously saved features.
164 | this.clearTileFeatureHash(layerCtx.id);
165 | }
166 |
167 | var features = vtl.parsedFeatures;
168 | for (var i = 0, len = features.length; i < len; i++) {
169 | var vtf = features[i]; //vector tile feature
170 | vtf.layer = vtl;
171 |
172 | /**
173 | * Apply filter on feature if there is one. Defined in the options object
174 | * of TileLayer.MVTSource.js
175 | */
176 | var filter = self.options.filter;
177 | if (typeof filter === 'function') {
178 | if ( filter(vtf, layerCtx) === false ) continue;
179 | }
180 |
181 | var getIDForLayerFeature;
182 | if (typeof self.options.getIDForLayerFeature === 'function') {
183 | getIDForLayerFeature = self.options.getIDForLayerFeature;
184 | } else {
185 | getIDForLayerFeature = Util.getIDForLayerFeature;
186 | }
187 | var uniqueID = self.options.getIDForLayerFeature(vtf) || i;
188 | var mvtFeature = self.features[uniqueID];
189 |
190 | /**
191 | * Use layerOrdering function to apply a zIndex property to each vtf. This is defined in
192 | * TileLayer.MVTSource.js. Used below to sort features.npm
193 | */
194 | var layerOrdering = self.options.layerOrdering;
195 | if (typeof layerOrdering === 'function') {
196 | layerOrdering(vtf, layerCtx); //Applies a custom property to the feature, which is used after we're thru iterating to sort
197 | }
198 |
199 | //Create a new MVTFeature if one doesn't already exist for this feature.
200 | if (!mvtFeature) {
201 | //Get a style for the feature - set it just once for each new MVTFeature
202 | var style = self.style(vtf);
203 |
204 | //create a new feature
205 | self.features[uniqueID] = mvtFeature = new MVTFeature(self, vtf, layerCtx, uniqueID, style);
206 | if (style && style.dynamicLabel && typeof style.dynamicLabel === 'function') {
207 | self.featuresWithLabels.push(mvtFeature);
208 | }
209 | } else {
210 | //Add the new part to the existing feature
211 | mvtFeature.addTileFeature(vtf, layerCtx);
212 | }
213 |
214 | //Associate & Save this feature with this tile for later
215 | if(layerCtx && layerCtx.id) self._canvasIDToFeatures[layerCtx.id]['features'].push(mvtFeature);
216 |
217 | }
218 |
219 | /**
220 | * Apply sorting (zIndex) on feature if there is a function defined in the options object
221 | * of TileLayer.MVTSource.js
222 | */
223 | var layerOrdering = self.options.layerOrdering;
224 | if (layerOrdering) {
225 | //We've assigned the custom zIndex property when iterating above. Now just sort.
226 | self._canvasIDToFeatures[layerCtx.id].features = self._canvasIDToFeatures[layerCtx.id].features.sort(function(a, b) {
227 | return -(b.properties.zIndex - a.properties.zIndex)
228 | });
229 | }
230 |
231 | self.redrawTile(layerCtx.id);
232 | },
233 |
234 | setStyle: function(styleFn) {
235 | // refresh the number for the highest count value
236 | // this is used only for choropleth
237 | this._highestCount = 0;
238 |
239 | // lowest count should not be 0, since we want to figure out the lowest
240 | this._lowestCount = null;
241 |
242 | this.style = styleFn;
243 | for (var key in this.features) {
244 | var feat = this.features[key];
245 | feat.setStyle(styleFn);
246 | }
247 | var z = this.map.getZoom();
248 | for (var key in this._tiles) {
249 | var id = z + ':' + key;
250 | this.redrawTile(id);
251 | }
252 | },
253 |
254 | /**
255 | * As counts for choropleths come in with the ajax data,
256 | * we want to keep track of which value is the highest
257 | * to create the color ramp for the fills of polygons.
258 | * @param count
259 | */
260 | setHighestCount: function(count) {
261 | if (count > this._highestCount) {
262 | this._highestCount = count;
263 | }
264 | },
265 |
266 | /**
267 | * Returns the highest number of all of the counts that have come in
268 | * from setHighestCount. This is assumed to be set via ajax callbacks.
269 | * @returns {number}
270 | */
271 | getHighestCount: function() {
272 | return this._highestCount;
273 | },
274 |
275 | setLowestCount: function(count) {
276 | if (!this._lowestCount || count < this._lowestCount) {
277 | this._lowestCount = count;
278 | }
279 | },
280 |
281 | getLowestCount: function() {
282 | return this._lowestCount;
283 | },
284 |
285 | setCountRange: function(count) {
286 | this.setHighestCount(count);
287 | this.setLowestCount(count);
288 | },
289 |
290 | //This is the old way. It works, but is slow for mouseover events. Fine for click events.
291 | handleClickEvent: function(evt, cb) {
292 | //Click happened on the GroupLayer (Manager) and passed it here
293 | var tileID = evt.tileID.split(":").slice(1, 3).join(":");
294 | var zoom = evt.tileID.split(":")[0];
295 | var canvas = this._tiles[tileID];
296 | if(!canvas) {
297 | //break out
298 | cb(evt);
299 | return;
300 | }
301 | var x = evt.layerPoint.x - canvas._leaflet_pos.x;
302 | var y = evt.layerPoint.y - canvas._leaflet_pos.y;
303 |
304 | var tilePoint = {x: x, y: y};
305 | var features = this._canvasIDToFeatures[evt.tileID].features;
306 |
307 | var minDistance = Number.POSITIVE_INFINITY;
308 | var nearest = null;
309 | var j, paths, distance;
310 |
311 | for (var i = 0; i < features.length; i++) {
312 | var feature = features[i];
313 | switch (feature.type) {
314 |
315 | case 1: //Point - currently rendered as circular paths. Intersect with that.
316 |
317 | //Find the radius of the point.
318 | var radius = 3;
319 | if (typeof feature.style.radius === 'function') {
320 | radius = feature.style.radius(zoom); //Allows for scale dependent rednering
321 | }
322 | else{
323 | radius = feature.style.radius;
324 | }
325 |
326 | paths = feature.getPathsForTile(evt.tileID);
327 | for (j = 0; j < paths.length; j++) {
328 | //Builds a circle of radius feature.style.radius (assuming circular point symbology).
329 | if(in_circle(paths[j][0].x, paths[j][0].y, radius, x, y)){
330 | nearest = feature;
331 | minDistance = 0;
332 | }
333 | }
334 | break;
335 |
336 | case 2: //LineString
337 | paths = feature.getPathsForTile(evt.tileID);
338 | for (j = 0; j < paths.length; j++) {
339 | if (feature.style) {
340 | var distance = this._getDistanceFromLine(tilePoint, paths[j]);
341 | var thickness = (feature.selected && feature.style.selected ? feature.style.selected.size : feature.style.size);
342 | if (distance < thickness / 2 + this.options.lineClickTolerance && distance < minDistance) {
343 | nearest = feature;
344 | minDistance = distance;
345 | }
346 | }
347 | }
348 | break;
349 |
350 | case 3: //Polygon
351 | paths = feature.getPathsForTile(evt.tileID);
352 | for (j = 0; j < paths.length; j++) {
353 | if (this._isPointInPoly(tilePoint, paths[j])) {
354 | nearest = feature;
355 | minDistance = 0; // point is inside the polygon, so distance is zero
356 | }
357 | }
358 | break;
359 | }
360 | if (minDistance == 0) break;
361 | }
362 |
363 | if (nearest && nearest.toggleEnabled) {
364 | nearest.toggle();
365 | }
366 | evt.feature = nearest;
367 | cb(evt);
368 | },
369 |
370 | clearTile: function(id) {
371 | //id is the entire zoom:x:y. we just want x:y.
372 | var ca = id.split(":");
373 | var canvasId = ca[1] + ":" + ca[2];
374 | if (typeof this._tiles[canvasId] === 'undefined') {
375 | console.error("typeof this._tiles[canvasId] === 'undefined'");
376 | return;
377 | }
378 | var canvas = this._tiles[canvasId];
379 |
380 | var context = canvas.getContext('2d');
381 | context.clearRect(0, 0, canvas.width, canvas.height);
382 | },
383 |
384 | clearTileFeatureHash: function(canvasID){
385 | this._canvasIDToFeatures[canvasID] = { features: []}; //Get rid of all saved features
386 | },
387 |
388 | clearLayerFeatureHash: function(){
389 | this.features = {};
390 | },
391 |
392 | redrawTile: function(canvasID) {
393 | //First, clear the canvas
394 | this.clearTile(canvasID);
395 |
396 | // If the features are not in the tile, then there is nothing to redraw.
397 | // This may happen if you call redraw before features have loaded and initially
398 | // drawn the tile.
399 | var featfeats = this._canvasIDToFeatures[canvasID];
400 | if (!featfeats) {
401 | return;
402 | }
403 |
404 | //Get the features for this tile, and redraw them.
405 | var features = featfeats.features;
406 |
407 | // we want to skip drawing the selected features and draw them last
408 | var selectedFeatures = [];
409 |
410 | // drawing all of the non-selected features
411 | for (var i = 0; i < features.length; i++) {
412 | var feature = features[i];
413 | if (feature.selected) {
414 | selectedFeatures.push(feature);
415 | } else {
416 | feature.draw(canvasID);
417 | }
418 | }
419 |
420 | // drawing the selected features last
421 | for (var j = 0, len2 = selectedFeatures.length; j < len2; j++) {
422 | var selFeat = selectedFeatures[j];
423 | selFeat.draw(canvasID);
424 | }
425 | },
426 |
427 | _resetCanvasIDToFeatures: function(canvasID, canvas) {
428 |
429 | this._canvasIDToFeatures[canvasID] = {};
430 | this._canvasIDToFeatures[canvasID].features = [];
431 | this._canvasIDToFeatures[canvasID].canvas = canvas;
432 |
433 | },
434 |
435 | linkedLayer: function() {
436 | if(this.mvtSource.layerLink) {
437 | var linkName = this.mvtSource.layerLink(this.name);
438 | return this.mvtSource.layers[linkName];
439 | }
440 | else{
441 | return null;
442 | }
443 | },
444 |
445 | featureWithLabelAdded: function(feature) {
446 | this.featuresWithLabels.push(feature);
447 | }
448 |
449 | });
450 |
451 |
452 | function removeLabels(self) {
453 | var features = self.featuresWithLabels;
454 | for (var i = 0, len = features.length; i < len; i++) {
455 | var feat = features[i];
456 | feat.removeLabel();
457 | }
458 | self.featuresWithLabels = [];
459 | }
460 |
461 | function in_circle(center_x, center_y, radius, x, y) {
462 | var square_dist = Math.pow((center_x - x), 2) + Math.pow((center_y - y), 2);
463 | return square_dist <= Math.pow(radius, 2);
464 | }
465 | /**
466 | * See https://github.com/ariya/phantomjs/blob/master/examples/waitfor.js
467 | *
468 | * Wait until the test condition is true or a timeout occurs. Useful for waiting
469 | * on a server response or for a ui change (fadeIn, etc.) to occur.
470 | *
471 | * @param testFx javascript condition that evaluates to a boolean,
472 | * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
473 | * as a callback function.
474 | * @param onReady what to do when testFx condition is fulfilled,
475 | * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
476 | * as a callback function.
477 | * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
478 | */
479 | function waitFor(testFx, onReady, timeOutMillis) {
480 | var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
481 | start = new Date().getTime(),
482 | condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()), //< defensive code
483 | interval = setInterval(function () {
484 | if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
485 | // If not time-out yet and condition not yet fulfilled
486 | condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
487 | } else {
488 | if (!condition) {
489 | // If condition still not fulfilled (timeout but condition is 'false')
490 | console.log("'waitFor()' timeout");
491 | clearInterval(interval); //< Stop this interval
492 | typeof (onReady) === "string" ? eval(onReady) : onReady('timeout'); //< Do what it's supposed to do once the condition is fulfilled
493 | } else {
494 | // Condition fulfilled (timeout and/or condition is 'true')
495 | console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
496 | clearInterval(interval); //< Stop this interval
497 | typeof (onReady) === "string" ? eval(onReady) : onReady('success'); //< Do what it's supposed to do once the condition is fulfilled
498 | }
499 | }
500 | }, 50); //< repeat check every 50ms
501 | };
--------------------------------------------------------------------------------
/src/MVTSource.js:
--------------------------------------------------------------------------------
1 | var VectorTile = require('vector-tile').VectorTile;
2 | var Protobuf = require('pbf');
3 | var Point = require('point-geometry');
4 | var Util = require('./MVTUtil');
5 | var MVTLayer = require('./MVTLayer');
6 |
7 |
8 | module.exports = L.TileLayer.MVTSource = L.TileLayer.Canvas.extend({
9 |
10 | options: {
11 | debug: false,
12 | url: "", //URL TO Vector Tile Source,
13 | getIDForLayerFeature: function() {},
14 | tileSize: 256,
15 | visibleLayers: [],
16 | xhrHeaders: {}
17 | },
18 | layers: {}, //Keep a list of the layers contained in the PBFs
19 | processedTiles: {}, //Keep a list of tiles that have been processed already
20 | _eventHandlers: {},
21 | _triggerOnTilesLoadedEvent: true, //whether or not to fire the onTilesLoaded event when all of the tiles finish loading.
22 | _url: "", //internal URL property
23 |
24 | style: function(feature) {
25 | var style = {};
26 |
27 | var type = feature.type;
28 | switch (type) {
29 | case 1: //'Point'
30 | style.color = 'rgba(49,79,79,1)';
31 | style.radius = 5;
32 | style.selected = {
33 | color: 'rgba(255,255,0,0.5)',
34 | radius: 6
35 | };
36 | break;
37 | case 2: //'LineString'
38 | style.color = 'rgba(161,217,155,0.8)';
39 | style.size = 3;
40 | style.selected = {
41 | color: 'rgba(255,25,0,0.5)',
42 | size: 4
43 | };
44 | break;
45 | case 3: //'Polygon'
46 | style.color = 'rgba(49,79,79,1)';
47 | style.outline = {
48 | color: 'rgba(161,217,155,0.8)',
49 | size: 1
50 | };
51 | style.selected = {
52 | color: 'rgba(255,140,0,0.3)',
53 | outline: {
54 | color: 'rgba(255,140,0,1)',
55 | size: 2
56 | }
57 | };
58 | break;
59 | }
60 | return style;
61 | },
62 |
63 |
64 | initialize: function(options) {
65 | L.Util.setOptions(this, options);
66 |
67 | //a list of the layers contained in the PBFs
68 | this.layers = {};
69 |
70 | // tiles currently in the viewport
71 | this.activeTiles = {};
72 |
73 | // thats that have been loaded and drawn
74 | this.loadedTiles = {};
75 |
76 | this._url = this.options.url;
77 |
78 | /**
79 | * For some reason, Leaflet has some code that resets the
80 | * z index in the options object. I'm having trouble tracking
81 | * down exactly what does this and why, so for now, we should
82 | * just copy the value to this.zIndex so we can have the right
83 | * number when we make the subsequent MVTLayers.
84 | */
85 | this.zIndex = options.zIndex;
86 |
87 | if (typeof options.style === 'function') {
88 | this.style = options.style;
89 | }
90 |
91 | if (typeof options.ajaxSource === 'function') {
92 | this.ajaxSource = options.ajaxSource;
93 | }
94 |
95 | this.layerLink = options.layerLink;
96 |
97 | this._eventHandlers = {};
98 |
99 | this._tilesToProcess = 0; //store the max number of tiles to be loaded. Later, we can use this count to count down PBF loading.
100 | },
101 |
102 | redraw: function(triggerOnTilesLoadedEvent){
103 | //Only set to false if it actually is passed in as 'false'
104 | if (triggerOnTilesLoadedEvent === false) {
105 | this._triggerOnTilesLoadedEvent = false;
106 | }
107 |
108 | L.TileLayer.Canvas.prototype.redraw.call(this);
109 | },
110 |
111 | onAdd: function(map) {
112 | var self = this;
113 | self.map = map;
114 | L.TileLayer.Canvas.prototype.onAdd.call(this, map);
115 |
116 | var mapOnClickCallback = function(e) {
117 | self._onClick(e);
118 | };
119 |
120 | map.on('click', mapOnClickCallback);
121 |
122 | map.on("layerremove", function(e) {
123 | // check to see if the layer removed is this one
124 | // call a method to remove the child layers (the ones that actually have something drawn on them).
125 | if (e.layer._leaflet_id === self._leaflet_id && e.layer.removeChildLayers) {
126 | e.layer.removeChildLayers(map);
127 | map.off('click', mapOnClickCallback);
128 | }
129 | });
130 |
131 | self.addChildLayers(map);
132 |
133 | if (typeof DynamicLabel === 'function' ) {
134 | this.dynamicLabel = new DynamicLabel(map, this, {});
135 | }
136 |
137 | },
138 |
139 | drawTile: function(canvas, tilePoint, zoom) {
140 | var ctx = {
141 | id: [zoom, tilePoint.x, tilePoint.y].join(":"),
142 | canvas: canvas,
143 | tile: tilePoint,
144 | zoom: zoom,
145 | tileSize: this.options.tileSize
146 | };
147 |
148 | //Capture the max number of the tiles to load here. this._tilesToProcess is an internal number we use to know when we've finished requesting PBFs.
149 | if(this._tilesToProcess < this._tilesToLoad) this._tilesToProcess = this._tilesToLoad;
150 |
151 | var id = ctx.id = Util.getContextID(ctx);
152 | this.activeTiles[id] = ctx;
153 |
154 | if(!this.processedTiles[ctx.zoom]) this.processedTiles[ctx.zoom] = {};
155 |
156 | if (this.options.debug) {
157 | this._drawDebugInfo(ctx);
158 | }
159 | this._draw(ctx);
160 | },
161 |
162 | setOpacity:function(opacity) {
163 | this._setVisibleLayersStyle('opacity',opacity);
164 | },
165 |
166 | setZIndex:function(zIndex) {
167 | this._setVisibleLayersStyle('zIndex',zIndex);
168 | },
169 |
170 | _setVisibleLayersStyle:function(style, value) {
171 | for(var key in this.layers) {
172 | this.layers[key]._tileContainer.style[style] = value;
173 | }
174 | },
175 |
176 | _drawDebugInfo: function(ctx) {
177 | var max = this.options.tileSize;
178 | var g = ctx.canvas.getContext('2d');
179 | g.strokeStyle = '#000000';
180 | g.fillStyle = '#FFFF00';
181 | g.strokeRect(0, 0, max, max);
182 | g.font = "12px Arial";
183 | g.fillRect(0, 0, 5, 5);
184 | g.fillRect(0, max - 5, 5, 5);
185 | g.fillRect(max - 5, 0, 5, 5);
186 | g.fillRect(max - 5, max - 5, 5, 5);
187 | g.fillRect(max / 2 - 5, max / 2 - 5, 10, 10);
188 | g.strokeText(ctx.zoom + ' ' + ctx.tile.x + ' ' + ctx.tile.y, max / 2 - 30, max / 2 - 10);
189 | },
190 |
191 | _draw: function(ctx) {
192 | var self = this;
193 |
194 | // //This works to skip fetching and processing tiles if they've already been processed.
195 | // var vectorTile = this.processedTiles[ctx.zoom][ctx.id];
196 | // //if we've already parsed it, don't get it again.
197 | // if(vectorTile){
198 | // console.log("Skipping fetching " + ctx.id);
199 | // self.checkVectorTileLayers(parseVT(vectorTile), ctx, true);
200 | // self.reduceTilesToProcessCount();
201 | // return;
202 | // }
203 |
204 | if (!this._url) return;
205 | var src = this.getTileUrl({ x: ctx.tile.x, y: ctx.tile.y, z: ctx.zoom });
206 |
207 | var xhr = new XMLHttpRequest();
208 | xhr.onload = function() {
209 | if (xhr.status == "200") {
210 |
211 | if(!xhr.response) return;
212 |
213 | var arrayBuffer = new Uint8Array(xhr.response);
214 | var buf = new Protobuf(arrayBuffer);
215 | var vt = new VectorTile(buf);
216 | //Check the current map layer zoom. If fast zooming is occurring, then short circuit tiles that are for a different zoom level than we're currently on.
217 | if(self.map && self.map.getZoom() != ctx.zoom) {
218 | console.log("Fetched tile for zoom level " + ctx.zoom + ". Map is at zoom level " + self._map.getZoom());
219 | return;
220 | }
221 | self.checkVectorTileLayers(parseVT(vt), ctx);
222 | tileLoaded(self, ctx);
223 | }
224 |
225 | //either way, reduce the count of tilesToProcess tiles here
226 | self.reduceTilesToProcessCount();
227 | };
228 |
229 | xhr.onerror = function() {
230 | console.log("xhr error: " + xhr.status)
231 | };
232 |
233 | xhr.open('GET', src, true); //async is true
234 | var headers = self.options.xhrHeaders;
235 | for (var header in headers) {
236 | xhr.setRequestHeader(header, headers[header])
237 | }
238 | xhr.responseType = 'arraybuffer';
239 | xhr.send();
240 | },
241 |
242 | reduceTilesToProcessCount: function(){
243 | this._tilesToProcess--;
244 | if(!this._tilesToProcess){
245 | //Trigger event letting us know that all PBFs have been loaded and processed (or 404'd).
246 | if(this._eventHandlers["PBFLoad"]) this._eventHandlers["PBFLoad"]();
247 | this._pbfLoaded();
248 | }
249 | },
250 |
251 | checkVectorTileLayers: function(vt, ctx, parsed) {
252 | var self = this;
253 |
254 | //Check if there are specified visible layers
255 | if(self.options.visibleLayers && self.options.visibleLayers.length > 0){
256 | //only let thru the layers listed in the visibleLayers array
257 | for(var i=0; i < self.options.visibleLayers.length; i++){
258 | var layerName = self.options.visibleLayers[i];
259 | if(vt.layers[layerName]){
260 | //Proceed with parsing
261 | self.prepareMVTLayers(vt.layers[layerName], layerName, ctx, parsed);
262 | }
263 | }
264 | }else{
265 | //Parse all vt.layers
266 | for (var key in vt.layers) {
267 | self.prepareMVTLayers(vt.layers[key], key, ctx, parsed);
268 | }
269 | }
270 | },
271 |
272 | prepareMVTLayers: function(lyr ,key, ctx, parsed) {
273 | var self = this;
274 |
275 | if (!self.layers[key]) {
276 | //Create MVTLayer or MVTPointLayer for user
277 | self.layers[key] = self.createMVTLayer(key, lyr.parsedFeatures[0].type || null);
278 | }
279 |
280 | if (parsed) {
281 | //We've already parsed it. Go get canvas and draw.
282 | self.layers[key].getCanvas(ctx, lyr);
283 | } else {
284 | self.layers[key].parseVectorTileLayer(lyr, ctx);
285 | }
286 |
287 | },
288 |
289 | createMVTLayer: function(key, type) {
290 | var self = this;
291 |
292 | var getIDForLayerFeature;
293 | if (typeof self.options.getIDForLayerFeature === 'function') {
294 | getIDForLayerFeature = self.options.getIDForLayerFeature;
295 | } else {
296 | getIDForLayerFeature = Util.getIDForLayerFeature;
297 | }
298 |
299 | var options = {
300 | getIDForLayerFeature: getIDForLayerFeature,
301 | filter: self.options.filter,
302 | layerOrdering: self.options.layerOrdering,
303 | style: self.style,
304 | name: key,
305 | asynch: true
306 | };
307 |
308 | if (self.options.zIndex) {
309 | options.zIndex = self.zIndex;
310 | }
311 |
312 | //Take the layer and create a new MVTLayer or MVTPointLayer if one doesn't exist.
313 | var layer = new MVTLayer(self, options).addTo(self.map);
314 |
315 | return layer;
316 | },
317 |
318 | getLayers: function() {
319 | return this.layers;
320 | },
321 |
322 | hideLayer: function(id) {
323 | if (this.layers[id]) {
324 | this._map.removeLayer(this.layers[id]);
325 | if(this.options.visibleLayers.indexOf("id") > -1){
326 | this.visibleLayers.splice(this.options.visibleLayers.indexOf("id"), 1);
327 | }
328 | }
329 | },
330 |
331 | showLayer: function(id) {
332 | if (this.layers[id]) {
333 | this._map.addLayer(this.layers[id]);
334 | if(this.options.visibleLayers.indexOf("id") == -1){
335 | this.visibleLayers.push(id);
336 | }
337 | }
338 | //Make sure manager layer is always in front
339 | this.bringToFront();
340 | },
341 |
342 | removeChildLayers: function(map){
343 | //Remove child layers of this group layer
344 | for (var key in this.layers) {
345 | var layer = this.layers[key];
346 | map.removeLayer(layer);
347 | }
348 | },
349 |
350 | addChildLayers: function(map) {
351 | var self = this;
352 | if(self.options.visibleLayers.length > 0){
353 | //only let thru the layers listed in the visibleLayers array
354 | for(var i=0; i < self.options.visibleLayers.length; i++){
355 | var layerName = self.options.visibleLayers[i];
356 | var layer = this.layers[layerName];
357 | if(layer){
358 | //Proceed with parsing
359 | map.addLayer(layer);
360 | }
361 | }
362 | }else{
363 | //Add all layers
364 | for (var key in this.layers) {
365 | var layer = this.layers[key];
366 | // layer is set to visible and is not already on map
367 | if (!layer._map) {
368 | map.addLayer(layer);
369 | }
370 | }
371 | }
372 | },
373 |
374 | bind: function(eventType, callback) {
375 | this._eventHandlers[eventType] = callback;
376 | },
377 |
378 | _onClick: function(evt) {
379 | //Here, pass the event on to the child MVTLayer and have it do the hit test and handle the result.
380 | var self = this;
381 | var onClick = self.options.onClick;
382 | var clickableLayers = self.options.clickableLayers;
383 | var layers = self.layers;
384 |
385 | evt.tileID = getTileURL(evt.latlng.lat, evt.latlng.lng, this.map.getZoom());
386 |
387 | // We must have an array of clickable layers, otherwise, we just pass
388 | // the event to the public onClick callback in options.
389 |
390 | if(!clickableLayers){
391 | clickableLayers = Object.keys(self.layers);
392 | }
393 |
394 | if (clickableLayers && clickableLayers.length > 0) {
395 | for (var i = 0, len = clickableLayers.length; i < len; i++) {
396 | var key = clickableLayers[i];
397 | var layer = layers[key];
398 | if (layer) {
399 | layer.handleClickEvent(evt, function(evt) {
400 | if (typeof onClick === 'function') {
401 | onClick(evt);
402 | }
403 | });
404 | }
405 | }
406 | } else {
407 | if (typeof onClick === 'function') {
408 | onClick(evt);
409 | }
410 | }
411 |
412 | },
413 |
414 | setFilter: function(filterFunction, layerName) {
415 | //take in a new filter function.
416 | //Propagate to child layers.
417 |
418 | //Add filter to all child layers if no layer is specified.
419 | for (var key in this.layers) {
420 | var layer = this.layers[key];
421 |
422 | if (layerName){
423 | if(key.toLowerCase() == layerName.toLowerCase()){
424 | layer.options.filter = filterFunction; //Assign filter to child layer, only if name matches
425 | //After filter is set, the old feature hashes are invalid. Clear them for next draw.
426 | layer.clearLayerFeatureHash();
427 | //layer.clearTileFeatureHash();
428 | }
429 | }
430 | else{
431 | layer.options.filter = filterFunction; //Assign filter to child layer
432 | //After filter is set, the old feature hashes are invalid. Clear them for next draw.
433 | layer.clearLayerFeatureHash();
434 | //layer.clearTileFeatureHash();
435 | }
436 | }
437 | },
438 |
439 | /**
440 | * Take in a new style function and propogate to child layers.
441 | * If you do not set a layer name, it resets the style for all of the layers.
442 | * @param styleFunction
443 | * @param layerName
444 | */
445 | setStyle: function(styleFn, layerName) {
446 | for (var key in this.layers) {
447 | var layer = this.layers[key];
448 | if (layerName) {
449 | if(key.toLowerCase() == layerName.toLowerCase()) {
450 | layer.setStyle(styleFn);
451 | }
452 | } else {
453 | layer.setStyle(styleFn);
454 | }
455 | }
456 | },
457 |
458 | featureSelected: function(mvtFeature) {
459 | if (this.options.mutexToggle) {
460 | if (this._selectedFeature) {
461 | this._selectedFeature.deselect();
462 | }
463 | this._selectedFeature = mvtFeature;
464 | }
465 | if (this.options.onSelect) {
466 | this.options.onSelect(mvtFeature);
467 | }
468 | },
469 |
470 | featureDeselected: function(mvtFeature) {
471 | if (this.options.mutexToggle && this._selectedFeature) {
472 | this._selectedFeature = null;
473 | }
474 | if (this.options.onDeselect) {
475 | this.options.onDeselect(mvtFeature);
476 | }
477 | },
478 |
479 | _pbfLoaded: function() {
480 | //Fires when all tiles from this layer have been loaded and drawn (or 404'd).
481 |
482 | //Make sure manager layer is always in front
483 | this.bringToFront();
484 |
485 | //See if there is an event to execute
486 | var self = this;
487 | var onTilesLoaded = self.options.onTilesLoaded;
488 |
489 | if (onTilesLoaded && typeof onTilesLoaded === 'function' && this._triggerOnTilesLoadedEvent === true) {
490 | onTilesLoaded(this);
491 | }
492 | self._triggerOnTilesLoadedEvent = true; //reset - if redraw() is called with the optinal 'false' parameter to temporarily disable the onTilesLoaded event from firing. This resets it back to true after a single time of firing as 'false'.
493 | }
494 |
495 | });
496 |
497 |
498 | if (typeof(Number.prototype.toRad) === "undefined") {
499 | Number.prototype.toRad = function() {
500 | return this * Math.PI / 180;
501 | }
502 | }
503 |
504 | function getTileURL(lat, lon, zoom) {
505 | var xtile = parseInt(Math.floor( (lon + 180) / 360 * (1<
3 | * on 8/15/14.
4 | */
5 | var Util = module.exports = {};
6 |
7 | Util.getContextID = function(ctx) {
8 | return [ctx.zoom, ctx.tile.x, ctx.tile.y].join(":");
9 | };
10 |
11 | /**
12 | * Default function that gets the id for a layer feature.
13 | * Sometimes this needs to be done in a different way and
14 | * can be specified by the user in the options for L.TileLayer.MVTSource.
15 | *
16 | * @param feature
17 | * @returns {ctx.id|*|id|string|jsts.index.chain.MonotoneChain.id|number}
18 | */
19 | Util.getIDForLayerFeature = function(feature) {
20 | return feature.properties.id;
21 | };
22 |
23 | Util.getJSON = function(url, callback) {
24 | var xmlhttp = typeof XMLHttpRequest !== 'undefined' ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
25 | xmlhttp.onreadystatechange = function() {
26 | var status = xmlhttp.status;
27 | if (xmlhttp.readyState === 4 && status >= 200 && status < 300) {
28 | var json = JSON.parse(xmlhttp.responseText);
29 | callback(null, json);
30 | } else {
31 | callback( { error: true, status: status } );
32 | }
33 | };
34 | xmlhttp.open("GET", url, true);
35 | xmlhttp.send();
36 | };
37 |
--------------------------------------------------------------------------------
/src/StaticLabel/StaticLabel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Nicholas Hallahan
3 | * on 7/31/14.
4 | */
5 | var Util = require('../MVTUtil');
6 | module.exports = StaticLabel;
7 |
8 | function StaticLabel(mvtFeature, ctx, latLng, style) {
9 | var self = this;
10 | this.mvtFeature = mvtFeature;
11 | this.map = mvtFeature.map;
12 | this.zoom = ctx.zoom;
13 | this.latLng = latLng;
14 | this.selected = false;
15 |
16 | if (mvtFeature.linkedFeature) {
17 | var linkedFeature = mvtFeature.linkedFeature();
18 | if (linkedFeature && linkedFeature.selected) {
19 | self.selected = true;
20 | }
21 | }
22 |
23 | init(self, mvtFeature, ctx, latLng, style)
24 | }
25 |
26 | function init(self, mvtFeature, ctx, latLng, style) {
27 | var ajaxData = mvtFeature.ajaxData;
28 | var sty = self.style = style.staticLabel(mvtFeature, ajaxData);
29 | var icon = self.icon = L.divIcon({
30 | className: sty.cssClass || 'label-icon-text',
31 | html: sty.html,
32 | iconSize: sty.iconSize || [50,50]
33 | });
34 |
35 | self.marker = L.marker(latLng, {icon: icon}).addTo(self.map);
36 |
37 | if (self.selected) {
38 | self.marker._icon.classList.add(self.style.cssSelectedClass || 'label-icon-text-selected');
39 | }
40 |
41 | self.marker.on('click', function(e) {
42 | self.toggle();
43 | });
44 |
45 | self.map.on('zoomend', function(e) {
46 | var newZoom = e.target.getZoom();
47 | if (self.zoom !== newZoom) {
48 | self.map.removeLayer(self.marker);
49 | }
50 | });
51 | }
52 |
53 |
54 | StaticLabel.prototype.toggle = function() {
55 | if (this.selected) {
56 | this.deselect();
57 | } else {
58 | this.select();
59 | }
60 | };
61 |
62 | StaticLabel.prototype.select = function() {
63 | this.selected = true;
64 | this.marker._icon.classList.add(this.style.cssSelectedClass || 'label-icon-text-selected');
65 | var linkedFeature = this.mvtFeature.linkedFeature();
66 | if (!linkedFeature.selected) linkedFeature.select();
67 | };
68 |
69 | StaticLabel.prototype.deselect = function() {
70 | this.selected = false;
71 | this.marker._icon.classList.remove(this.style.cssSelectedClass || 'label-icon-text-selected');
72 | var linkedFeature = this.mvtFeature.linkedFeature();
73 | if (linkedFeature.selected) linkedFeature.deselect();
74 | };
75 |
76 | StaticLabel.prototype.remove = function() {
77 | if (!this.map || !this.marker) return;
78 | this.map.removeLayer(this.marker);
79 | };
80 |
--------------------------------------------------------------------------------
/src/StaticLabel/label.css:
--------------------------------------------------------------------------------
1 | .label-icon-number {
2 | background-color: #6eafff;
3 | display: inline-block;
4 | cursor: pointer;
5 | color: #ffffff;
6 | font-size: 14px;
7 | border-radius: 50%;
8 | text-align: center;
9 | vertical-align: middle;
10 | line-height: 33px;
11 | box-shadow: 0px 0px 0px 5px rgba(255,255,255,0.3);
12 | }
13 |
14 | .label-icon-number-lg {
15 | background-color: #6eafff;
16 | display: inline-block;
17 | cursor: pointer;
18 | color: #ffffff;
19 | font-size: 14px;
20 | border-radius: 50%;
21 | text-align: center;
22 | vertical-align: middle;
23 | line-height: 41px;
24 | box-shadow: 0px 0px 0px 5px rgba(255,255,255,0.3);
25 | }
26 |
27 | .label-icon-text {
28 | display: inline-block;
29 | cursor: pointer;
30 | font-size: 18px;
31 | line-height: 1;
32 | text-align: center;
33 | vertical-align: middle;
34 | text-shadow: 0px 1px 1px #232323;
35 | color: #ebf9f5;
36 | }
37 |
38 | /*.label-icon-text:hover {*/
39 | /*color: #EDB229;*/
40 | /*}*/
41 |
42 | .label-icon-text-selected {
43 | color: #d9534f;
44 | }
45 |
46 | .label-icon-number-selected {
47 | box-shadow: 0px 0px 0px 7px rgba(255,140,0,0.6);
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014, Spatial Development International
3 | * All rights reserved.
4 | *
5 | * Source code can be found at:
6 | * https://github.com/SpatialServer/Leaflet.MapboxVectorTile
7 | *
8 | * @license ISC
9 | */
10 |
11 | module.exports = require('./MVTSource');
12 |
--------------------------------------------------------------------------------
/test/fixtures/basicStaticLabel.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gadm2014kenya/{z}/{x}/{y}.pbf",
3 | debug: true,
4 | clickableLayers: ['gadm1'],
5 |
6 | getIDForLayerFeature: function(feature) {
7 | return feature.properties.id;
8 | },
9 |
10 | filter: function(feature, context) {
11 | if (feature.layer.name === 'gadm1_label' || feature.layer.name === 'gadm1') {
12 | return true;
13 | }
14 |
15 | return false;
16 | },
17 |
18 | layerLink: function(layerName) {
19 | if (layerName.indexOf('_label') > -1) {
20 | return layerName.replace('_label', '');
21 | }
22 | return layerName + '_label';
23 | },
24 |
25 | style: function(feature) {
26 | var style = {};
27 | var selected = style.selected = {};
28 |
29 | var type = feature.type;
30 | switch (type) {
31 | case 1: //'Point'
32 | // unselected
33 | style.color = '#ff0000';
34 | style.radius = 3;
35 | // selected
36 | selected.color = 'rgba(255,255,0,0.5)';
37 | selected.radius = 5;
38 | break;
39 | case 2: //'LineString'
40 | // unselected
41 | style.color = 'rgba(161,217,155,0.8)';
42 | style.size = 3;
43 | // selected
44 | selected.color = 'rgba(255,25,0,0.5)';
45 | selected.size = 3;
46 | break;
47 | case 3: //'Polygon'
48 | // unselected
49 | style.color = 'rgba(149,139,255,0.4)';
50 | style.outline = {
51 | color: 'rgb(20,20,20)',
52 | size: 2
53 | };
54 | // selected
55 | selected.color = 'rgba(255,25,0,0.3)';
56 | selected.outline = {
57 | color: '#d9534f',
58 | size: 3
59 | };
60 | }
61 |
62 | if (feature.layer.name === 'gadm1_label') {
63 | style.staticLabel = function() {
64 | var style = {
65 | html: feature.properties.name,
66 | iconSize: [125, 30],
67 | cssClass: 'label-icon-text'
68 | };
69 | return style;
70 | };
71 | }
72 |
73 | return style;
74 | }
75 | };
--------------------------------------------------------------------------------
/test/fixtures/confetti_datasource.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: "http://spatialserver.spatialdev.com/services/postgis/cicos_2014/geom/vector-tiles/{z}/{x}/{y}.pbf?fields=type,id", //Original Datasource - this is dynamic data, tho
3 | //url: "http://localhost/Leaflet.MapboxVectorTile/test/fixtures/pbfs/{z}.{x}.{y}.pbf",
4 | debug: true,
5 | clickableLayers: [''],
6 |
7 | getIDForLayerFeature: function (feature) {
8 | return feature.properties.id;
9 | },
10 |
11 | /**
12 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
13 | * to every property associated with a given feature (the feature, and the layer). You can also filter
14 | * based of the context (each tile that the feature is drawn onto).
15 | *
16 | * Returning false skips over the feature and it is not drawn.
17 | *
18 | * @param feature
19 | * @returns {boolean}
20 | */
21 | filter: function (feature, context) {
22 | //return feature.properties.type != 'Mobile Money Agent';
23 | return true;
24 | },
25 |
26 | /**
27 | * Specify which features should have a certain z index (integer). Lower numbers will draw on 'the bottom'.
28 | *
29 | * @param feature - the PBFFeature that contains properties
30 | */
31 | layerOrdering: function (feature) {
32 | //This only needs to be done for each type, not necessarily for each feature. But we'll start here.
33 | if (feature && feature.properties) {
34 | feature.properties.zIndex = 5;
35 | }
36 | },
37 |
38 | style: function (feature) {
39 | var style = {};
40 | var selected = style.selected = {};
41 |
42 | var type = feature.type;
43 | switch (type) {
44 | case 1: //'Point'
45 | // unselected
46 | style.color = '#3086AB';
47 | style.radius = 3;
48 | // selected
49 | selected.color = 'rgba(255,255,0,0.5)';
50 | selected.radius = 5;
51 | break;
52 | case 2: //'LineString'
53 | // unselected
54 | style.color = 'rgba(161,217,155,0.8)';
55 | style.size = 3;
56 | // selected
57 | selected.color = 'rgba(255,25,0,0.5)';
58 | selected.size = 3;
59 | break;
60 | case 3: //'Polygon'
61 | // unselected
62 | style.color = 'rgba(149,139,255,0.4)';
63 | style.outline = {
64 | color: 'rgb(20,20,20)',
65 | size: 2
66 | };
67 | // selected
68 | selected.color = 'rgba(255,25,0,0.3)';
69 | selected.outline = {
70 | color: '#d9534f',
71 | size: 3
72 | };
73 | }
74 |
75 | return style;
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/test/fixtures/indiaAggregationsMutex.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gaul_fsp_india/{z}/{x}/{y}.pbf",
3 | debug: true,
4 | clickableLayers: ['gaul_2014_adm1'],
5 |
6 | /**
7 | * If you click on a feature, if there is a different
8 | * currently selected feature, that gets toggled off.
9 | */
10 | mutexToggle: true,
11 |
12 | getIDForLayerFeature: function(feature) {
13 | return feature._id;
14 | },
15 |
16 | /**
17 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
18 | * to every property associated with a given feature (the feature, and the layer). You can also filter
19 | * based of the context (each tile that the feature is drawn onto).
20 | *
21 | * Returning false skips over the feature and it is not drawn.
22 | *
23 | * @param feature
24 | * @returns {boolean}
25 | */
26 | filter: function(feature, context) {
27 | if (feature.layer.name === 'gaul_2014_adm1' || feature.layer.name === 'gaul_2014_adm1_label') {
28 | return true;
29 | }
30 | return false;
31 | },
32 |
33 | style: function (feature) {
34 | var style = {};
35 |
36 | var type = feature.type;
37 | switch (type) {
38 | case 1: //'Point'
39 | style.color = 'rgba(49,79,79,1)';
40 | style.radius = 5;
41 | style.selected = {
42 | color: 'rgba(255,255,0,0.5)',
43 | radius: 6
44 | };
45 | break;
46 | case 2: //'LineString'
47 | style.color = 'rgba(161,217,155,0.8)';
48 | style.size = 3;
49 | style.selected = {
50 | color: 'rgba(255,25,0,0.5)',
51 | size: 4
52 | };
53 | break;
54 | case 3: //'Polygon'
55 | style.color = 'rgba(149,139,255,0.4)';
56 | style.outline = {
57 | color: 'rgb(20,20,20)',
58 | size: 1
59 | };
60 | style.selected = {
61 | color: 'rgba(255,140,0,0.3)',
62 | outline: {
63 | color: 'rgba(255,140,0,1)',
64 | size: 2
65 | }
66 | };
67 | break;
68 | }
69 |
70 | if (feature.layer.name === 'gaul_2014_adm1_label') {
71 | style.ajaxSource = function(mvtFeature) {
72 | var id = mvtFeature.id;
73 | return 'http://localhost:8888/fsp/2014/fsp/aggregations-no-name/' + id + '.json';
74 | };
75 |
76 | style.staticLabel = function(mvtFeature, ajaxData) {
77 | var style = {
78 | html: ajaxData.total_count,
79 | iconSize: [33,33],
80 | cssClass: 'label-icon-number',
81 | cssSelectedClass: 'label-icon-number-selected'
82 | };
83 | return style;
84 | };
85 | }
86 |
87 | return style;
88 | },
89 |
90 | /**
91 | * When we want to link events between layers, like clicking on a label and a
92 | * corresponding polygon freature, this will return the corresponding mapping
93 | * between layers. This provides knowledge of which other feature a given feature
94 | * is linked to.
95 | *
96 | * @param layerName the layer we want to know the linked layer from
97 | * @returns {string} returns corresponding linked layer
98 | */
99 | layerLink: function(layerName) {
100 | if (layerName.indexOf('_label') > -1) {
101 | return layerName.replace('_label','');
102 | }
103 | return layerName + '_label';
104 | }
105 |
106 | };
--------------------------------------------------------------------------------
/test/fixtures/indiaStaticLabel.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: "http://spatialserver.spatialdev.com/services/vector-tiles/gaul_fsp_india/{z}/{x}/{y}.pbf",
3 | debug: true,
4 | clickableLayers: ['gaul_2014_adm1'],
5 | getIDForLayerFeature: function(feature) {
6 | return feature._id;
7 | },
8 |
9 | /**
10 | * The filter function gets called when iterating though each vector tile feature (vtf). You have access
11 | * to every property associated with a given feature (the feature, and the layer). You can also filter
12 | * based of the context (each tile that the feature is drawn onto).
13 | *
14 | * Returning false skips over the feature and it is not drawn.
15 | *
16 | * @param feature
17 | * @returns {boolean}
18 | */
19 | filter: function(feature, context) {
20 | if (feature.layer.name === 'gaul_2014_adm1' || feature.layer.name === 'gaul_2014_adm1_label') {
21 | return true;
22 | }
23 | return false;
24 | },
25 |
26 | style: function (feature) {
27 | var style = {};
28 |
29 | var type = feature.type;
30 | switch (type) {
31 | case 1: //'Point'
32 | style.color = 'rgba(49,79,79,1)';
33 | style.radius = 5;
34 | style.selected = {
35 | color: 'rgba(255,255,0,0.5)',
36 | radius: 6
37 | };
38 | break;
39 | case 2: //'LineString'
40 | style.color = 'rgba(161,217,155,0.8)';
41 | style.size = 3;
42 | style.selected = {
43 | color: 'rgba(255,25,0,0.5)',
44 | size: 4
45 | };
46 | break;
47 | case 3: //'Polygon'
48 | style.color = 'rgba(149,139,255,0.4)';
49 | style.outline = {
50 | color: 'rgb(20,20,20)',
51 | size: 1
52 | };
53 | style.selected = {
54 | color: 'rgba(255,140,0,0.3)',
55 | outline: {
56 | color: 'rgba(255,140,0,1)',
57 | size: 2
58 | }
59 | };
60 | break;
61 | }
62 |
63 | if (feature.layer.name === 'gaul_2014_adm1_label') {
64 | style.staticLabel = function() {
65 | var style = {
66 | html: feature.properties.name,
67 | iconSize: [125,30],
68 | cssClass: 'label-icon-text'
69 | };
70 | return style;
71 | };
72 | }
73 |
74 | return style;
75 | },
76 |
77 | /**
78 | * When we want to link events between layers, like clicking on a label and a
79 | * corresponding polygon freature, this will return the corresponding mapping
80 | * between layers. This provides knowledge of which other feature a given feature
81 | * is linked to.
82 | *
83 | * @param layerName the layer we want to know the linked layer from
84 | * @returns {string} returns corresponding linked layer
85 | */
86 | layerLink: function(layerName) {
87 | if (layerName.indexOf('_label') > -1) {
88 | return layerName.replace('_label','');
89 | }
90 | return layerName + '_label';
91 | }
92 |
93 | };
--------------------------------------------------------------------------------
/test/fixtures/pbfs/13.5889.3494.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/test/fixtures/pbfs/13.5889.3494.pbf
--------------------------------------------------------------------------------
/test/fixtures/pbfs/14.11875.6922.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpatialServer/Leaflet.MapboxVectorTile/f74f895ea3137eda70360ab2ca4319dcce97a1a8/test/fixtures/pbfs/14.11875.6922.pbf
--------------------------------------------------------------------------------
/test/js/MVTSource.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Nicholas Hallahan
3 | * on 9/12/14.
4 | */
5 |
6 | var test = require('tape').test;
7 | var MVTSource = require('../../src/MVTSource');
8 |
9 |
10 | test('creating MVTSource object', function(t) {
11 | var basicOpts = require('../fixtures/basicStaticLabel.js');
12 | t.plan(1);
13 | var mvtSource = new MVTSource(basicOpts);
14 | t.ok(mvtSource, 'gadm2014kenya with static labels source created');
15 | });
16 |
17 | test('create a map, loading a tile', function(t) {
18 | var basicOpts = require('../fixtures/basicStaticLabel.js');
19 | document.body.innerHTML += '';
20 | var map = L.map('map').setView([0,39], 6); // africa
21 | var mvtSource = new MVTSource(basicOpts);
22 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
23 | maxZoom: 18
24 | }).addTo(map);
25 | map.addLayer(mvtSource);
26 |
27 | t.plan(1);
28 | setTimeout(function(mvtSource) {
29 | t.ok(mvtSource.loadedTiles['6:38:32'], 'tile 6:38:32 loaded');
30 | }, 2000, mvtSource);
31 | });
32 |
33 | test('basic map, zoom in, zoom out, check for drawn tiles', function(t) {
34 | var basicOpts = require('../fixtures/basicStaticLabel.js');
35 | document.body.innerHTML += '';
36 | var map = L.map('map').setView([0,39], 6); // africa
37 | var mvtSource = new MVTSource(basicOpts);
38 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
39 | maxZoom: 18
40 | }).addTo(map);
41 | map.addLayer(mvtSource);
42 |
43 | map.zoomIn();
44 | map.zoomOut();
45 |
46 | t.plan(2);
47 | setTimeout(function(mvtSource) {
48 | var numTiles = Object.keys(mvtSource.loadedTiles).length;
49 | t.equal(numTiles, 6, "# Tiles: " + numTiles);
50 | t.ok(mvtSource.loadedTiles['6:38:32'], 'tile 6:38:32 loaded');
51 | }, 2000, mvtSource);
52 | });
53 |
54 | test('ensure labels are removed when mvtSource is removed from map', function(t) {
55 | var opts = require('../fixtures/indiaStaticLabel.js');
56 | document.body.innerHTML += '';
57 | var map = L.map('map').setView([25.40,79.409], 6); // Northern India
58 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
59 | maxZoom: 18
60 | }).addTo(map);
61 | var mvtSource = new L.TileLayer.MVTSource(opts);
62 | map.addLayer(mvtSource);
63 |
64 | t.plan(1);
65 | setTimeout(function(mvtSource) {
66 | // var layersWithLabels = Object.keys(map._layers).length;
67 | // t.equal(layersWithLabels, 41, 'should be 41 layers on map');
68 | setTimeout(function(mvtSource) {
69 | map.removeLayer(mvtSource);
70 | var layersWithLabels = Object.keys(map._layers).length;
71 | t.equal(layersWithLabels, 1, 'should be 1 base map layer with no mvt source and no labels');
72 | }, 500, mvtSource);
73 | }, 700, mvtSource);
74 | });
75 |
76 | test('ensure no repeats of features for featuresWithLabels array', function(t) {
77 | var opts = require('../fixtures/indiaStaticLabel.js');
78 | document.body.innerHTML += '';
79 | var map = L.map('map').setView([25.40,79.409], 4); // Northern India
80 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
81 | maxZoom: 18
82 | }).addTo(map);
83 | var mvtSource = new L.TileLayer.MVTSource(opts);
84 | map.addLayer(mvtSource);
85 |
86 | t.plan(1);
87 | var hash = {};
88 | setTimeout(function(mvtSource) {
89 | var featuresWithLabels = mvtSource.layers.gaul_2014_adm1_label.featuresWithLabels;
90 | for (var idx = 0, len = featuresWithLabels.length; idx < len; idx++) {
91 | var feat = featuresWithLabels[idx];
92 | if (hash[feat.id]) {
93 | t.fail('there is more than 1 feature with id ' + feat.id + ' also known as ' + feat.staticLabel.icon.options.html);
94 | }
95 | hash[feat.id] = true;
96 | // console.log(feat.staticLabel.icon.options.html);
97 | }
98 | t.pass();
99 | }, 1500, mvtSource);
100 | });
101 |
102 |
103 | // NH TODO: Some strange error happens with this test, look into further...
104 | // not ok 1 Error: TypeError: 'undefined' is not a function (evaluating 'self.callback.bind()') on line 27079
105 | //test('ensure mutex toggle works', function(t) {
106 | // var opts = require('../fixtures/indiaAggregationsMutex.js');
107 | // document.body.innerHTML += '';
108 | // var map = L.map('map').setView([25.40,79.409], 4); // Northern India
109 | // L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
110 | // maxZoom: 18
111 | // }).addTo(map);
112 | // var mvtSource = new L.TileLayer.MVTSource(opts);
113 | // map.addLayer(mvtSource);
114 | //
115 | // t.plan(1);
116 | // setTimeout(function(mvtSource) {
117 | // var features = mvtSource.layers.gaul_2014_adm1.features;
118 | // var idx = 0;
119 | // for (var featId in features) {
120 | // ++idx;
121 | // if (id > 3) {
122 | // break;
123 | // }
124 | // var feat = features[featId];
125 | // feat.toggle();
126 | // }
127 | //
128 | // var selectedFeaturesCount = 0;
129 | // for (var featId in features) {
130 | // var feat = features[featId];
131 | // if (feat.selected) {
132 | // ++selectedFeaturesCount;
133 | // }
134 | // }
135 | //
136 | // t.equal(selectedFeaturesCount, 1, 'there should only be one selected feature');
137 | //
138 | // }, 1500, mvtSource);
139 | //});
140 |
--------------------------------------------------------------------------------
/test/js/MVTSource_confetti.test.js:
--------------------------------------------------------------------------------
1 | var test = require('tape').test;
2 | var MVTSource = require('../../src/MVTSource');
3 |
4 | test('create a confetti map, loading a confetti tile', function(t) {
5 | console.log("About to load datasource");
6 | var confetti = require('../fixtures/confetti_datasource.js');
7 | document.body.innerHTML += '';
8 |
9 | var map = L.map('map').setView([26.85305,80.93765], 14); // india
10 | console.log("Created Map");
11 |
12 | var mvtSource = new MVTSource(confetti);
13 | console.log("Created new source.");
14 |
15 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
16 | maxZoom: 18
17 | }).addTo(map);
18 | map.addLayer(mvtSource);
19 |
20 | console.log("Added source to map.");
21 | console.log("Center: " + map.getCenter());
22 |
23 | t.plan(1);
24 |
25 | setTimeout(function(mvtSource) {
26 | t.ok(mvtSource.loadedTiles['14:11875:6922'], 'tile 14:11875:6922 loaded');
27 | }, 2000, mvtSource);
28 | });
29 |
30 |
31 | test('create a confetti map, filter confetti, test output - clearTile', function(t) {
32 | var confetti = require('../fixtures/confetti_datasource.js');
33 | document.body.innerHTML += '';
34 | var map = L.map('map').setView([25.53082458,78.816175], 13); // india
35 | var mvtSource = new MVTSource(confetti);
36 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
37 | maxZoom: 18
38 | }).addTo(map);
39 | map.addLayer(mvtSource);
40 |
41 | //Make sure tile loads
42 | t.plan(2);
43 | setTimeout(function(mvtSource) {
44 | t.ok(mvtSource.loadedTiles['13:5889:3494'], 'tile 13:5889:3494 loaded');
45 | }, 2000, mvtSource);
46 |
47 | //Now filter the features
48 | var tileID = '13:5889:3494';
49 | setTimeout(function(mvtSource) {
50 | var lyr = mvtSource.layers[Object.keys(mvtSource.getLayers())[0]]; //Get the 1st Layer
51 | var features = lyr._canvasIDToFeatures[tileID].features;
52 | console.log("# Features: " + features.length);
53 |
54 | t.equal(features.length, 1);
55 | }, 2000, mvtSource);
56 | });
57 |
58 | test('Add confetti layer, remove layer, add it back.', function(t) {
59 | var confetti = require('../fixtures/confetti_datasource.js');
60 | document.body.innerHTML += '';
61 | var map = L.map('map').setView([25.53082458,78.816175], 13); // india
62 | var mvtSource = new MVTSource(confetti);
63 | L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
64 | maxZoom: 18
65 | }).addTo(map);
66 | map.addLayer(mvtSource);
67 | console.log("added layer");
68 |
69 | //Set up remove event so we know when it is gone
70 | map.on("layerremove", function(removedObject){
71 |
72 | if(removedObject.layer.name && removedObject.layer.name == 'cicos_2014_geom') {
73 |
74 | //When layer is removed, this is fired.
75 | console.log("removed, about to add back.");
76 |
77 | //Add it back
78 | map.addLayer(mvtSource);
79 |
80 | console.log("added layer...again")
81 | setTimeout(function (addedLayer) {
82 | var tileID = '13:5889:3494';
83 | var lyr = addedLayer.layers[Object.keys(addedLayer.getLayers())[0]]; //Get the 1st Layer
84 | var features = lyr._canvasIDToFeatures[tileID].features;
85 | console.log("2nd Loading - # Features: " + features.length);
86 | t.equal(features.length, 1);
87 | }, 2000, mvtSource);
88 | }
89 |
90 | });
91 |
92 |
93 | //Make sure tile loads
94 | t.plan(2);
95 |
96 | setTimeout(function(mvtSource) {
97 |
98 | t.ok(mvtSource.loadedTiles['13:5889:3494'], 'tile 13:5889:3494 loaded');
99 |
100 | console.log("about to remove");
101 | //Remove it.
102 | map.removeLayer(mvtSource);
103 |
104 | }, 2000, mvtSource);
105 |
106 |
107 | });
--------------------------------------------------------------------------------
/test/visibletests/confetti_test.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------