├── README.md ├── fail2map-action.conf ├── fail2map.py ├── index.html ├── js ├── QuadTree.js ├── heatmap-leaflet.js ├── heatmap.js ├── leaflet-providers.js ├── leaflet.label.js └── maps.js ├── places.geojson └── style.css /README.md: -------------------------------------------------------------------------------- 1 | Fail2map 2 | ======== 3 | 4 | Fail2map is a map generator for [fail2ban](http://www.fail2ban.org). 5 | It displays banned IP on a world map. Adding IP is done automagically through a fail2ban *action*. 6 | 7 | Fail2map is based on [backpack](https://github.com/maximeh/backpack) by Maxime Hadjinlian. 8 | 9 | ### Try the [example](http://mvonthron.github.io/fail2map). 10 | 11 | Installing fail2map and fail2ban action 12 | --------------------------------------- 13 | 1. Place fail2map in the desired path of your web server 14 | 15 | git clone https://github.com/mvonthron/fail2map ~/public_html/fail2map 16 | 17 | 2. Edit `fail2map-action.conf` with the correct path for fail2map.py 18 | 19 | fail2map-action.conf:20 fail2map = cd /home//public_html/fail2map && python fail2map.py 20 | 21 | 3. Move/copy/link `fail2map-action.conf` to fail2ban actions folder (usually `/etc/fail2ban/action.d/`) 22 | 4. Add the action to your `jail.conf` or `jail.local` 23 | 24 | # The simplest action to take: ban only 25 | action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] 26 | fail2map-action 27 | 28 | 5. (Optional) Change the tile provider in js/maps.js line 45: 29 | 30 | baseLayer = L.tileLayer.provider('Thunderforest.Landscape', ... 31 | ^= select any provider listed at http://leaflet-extras.github.io/leaflet-providers/preview/ 32 | 33 | Notes 34 | ----- 35 | * OpenStreetMap tiles providers list by https://github.com/leaflet-extras/leaflet-providers 36 | * Map API from [leaflet](http://www.leafletjs.com) 37 | * IP geolocation is provided by [Telize](http://http://www.telize.com/). It's free, but not very accurate. If you want to achieve high precision, you may want a paid account at maxmind.com and change `GEOIP_API` in `fail2map.py`. 38 | 39 | 40 | 41 | ---- 42 | 2015, Manuel Vonthron 43 | 44 | -------------------------------------------------------------------------------- /fail2map-action.conf: -------------------------------------------------------------------------------- 1 | # Fail2Ban configuration file 2 | # 3 | # Author: Manuel Vonthron 4 | # 5 | 6 | [Definition] 7 | # 8 | actionstart = 9 | # 10 | actionstop = 11 | # 12 | actioncheck = 13 | # 14 | actionban = add 15 | # 16 | actionunban = 17 | 18 | [Init] 19 | # 20 | fail2map = cd **FAIL2MAP PATH** && python fail2map.py 21 | 22 | -------------------------------------------------------------------------------- /fail2map.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python -B 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import os 6 | import sys 7 | import urllib2 8 | import random 9 | 10 | JSON_FILE = "places.geojson" 11 | GEOIP_API = "http://www.telize.com/geoip/%s" 12 | 13 | # free IP geolocation usually gives very approximate results 14 | # there is a big chance that we get the same place multiple times, so 15 | # we might want to add a small offset to GPS coordinates in order to 16 | # view the markers better 17 | ADD_RANDOM_OFFSET = True 18 | 19 | 20 | def find_lat_lng(ipaddr): 21 | point = { 22 | "type": "Feature", 23 | "geometry": {"type": "Point", "coordinates": [None, None]}, 24 | "properties": {"name": ipaddr, "place": "", "show_on_map": False} 25 | } 26 | req = urllib2.urlopen(GEOIP_API % ipaddr) 27 | geo_value = json.loads(req.read()) 28 | 29 | if len(geo_value) > 0: 30 | point["geometry"]["coordinates"] = [float(geo_value['longitude']), float(geo_value['latitude'])] 31 | point["properties"]["place"] = "{0}, {1}".format(geo_value.get('city', ''), geo_value.get('country', '')) 32 | point["properties"]["show_on_map"] = True 33 | 34 | return point 35 | 36 | 37 | def add(ipaddr): 38 | newPoint = find_lat_lng(ipaddr) 39 | 40 | # open GPS JSON file 41 | data = { 42 | "type": "FeatureCollection", 43 | "features": [], 44 | } 45 | 46 | try: 47 | with open(JSON_FILE, "r") as fh: 48 | data = json.loads(fh.read()) 49 | except: 50 | pass 51 | 52 | for place in data["features"]: 53 | if place["properties"]["name"] == newPoint["properties"]["name"]: 54 | print("We already have this IP in the file") 55 | return 0 56 | 57 | if ADD_RANDOM_OFFSET: 58 | if place["geometry"]["coordinates"] == newPoint["geometry"]["coordinates"]: 59 | newPoint["geometry"]["coordinates"][0] += random.uniform(-0.1, 0.1) 60 | newPoint["geometry"]["coordinates"][1] += random.uniform(-0.1, 0.1) 61 | 62 | data["features"].append(newPoint) 63 | 64 | with open(JSON_FILE, 'w') as fh: 65 | fh.write(json.dumps(data, sort_keys=True, indent=4)) 66 | 67 | return 0 68 | 69 | 70 | if __name__ == '__main__': 71 | if len(sys.argv) == 3: 72 | if sys.argv[1] == "add": 73 | sys.exit(add(sys.argv[2])) 74 | else: 75 | print("%s must be called with 'add' and a target IP as first and second arguments." % sys.argv[0]) 76 | print(" ex: python fail2map.py add 0.0.0.0") 77 | sys.exit(1) 78 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fail2map 5 | 6 | 7 | 10 | 11 | 12 | 13 |
14 | 15 | Fork me on GitHub 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /js/QuadTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2011 Mike Chambers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | /* 26 | From https://github.com/jsmarkus/ExamplesByMesh/tree/master/JavaScript/QuadTree, slightly modified by domoritz 27 | */ 28 | 29 | 30 | /** 31 | * A QuadTree implementation in JavaScript, a 2d spatial subdivision algorithm. 32 | * @module QuadTree 33 | **/ 34 | 35 | (function(window) { 36 | 37 | /****************** QuadTree ****************/ 38 | 39 | /** 40 | * QuadTree data structure. 41 | * @class QuadTree 42 | * @constructor 43 | * @param {Object} An object representing the bounds of the top level of the QuadTree. The object 44 | * should contain the following properties : x, y, width, height 45 | * @param {Boolean} pointQuad Whether the QuadTree will contain points (true), or items with bounds 46 | * (width / height)(false). Default value is false. 47 | * @param {Number} maxDepth The maximum number of levels that the quadtree will create. Default is 4. 48 | * @param {Number} maxChildren The maximum number of children that a node can contain before it is split into sub-nodes. 49 | **/ 50 | function QuadTree(bounds, pointQuad, maxDepth, maxChildren) 51 | { 52 | var node; 53 | if(pointQuad) 54 | { 55 | 56 | node = new Node(bounds, 0, maxDepth, maxChildren); 57 | } 58 | else 59 | { 60 | node = new BoundsNode(bounds, 0, maxDepth, maxChildren); 61 | } 62 | 63 | this.root = node; 64 | } 65 | 66 | /** 67 | * The root node of the QuadTree which covers the entire area being segmented. 68 | * @property root 69 | * @type Node 70 | **/ 71 | QuadTree.prototype.root = null; 72 | 73 | 74 | /** 75 | * Inserts an item into the QuadTree. 76 | * @method insert 77 | * @param {Object|Array} item The item or Array of items to be inserted into the QuadTree. The item should expose x, y 78 | * properties that represents its position in 2D space. 79 | **/ 80 | QuadTree.prototype.insert = function(item) 81 | { 82 | if(item instanceof Array) 83 | { 84 | var len = item.length; 85 | 86 | for(var i = 0; i < len; i++) 87 | { 88 | this.root.insert(item[i]); 89 | } 90 | } 91 | else 92 | { 93 | this.root.insert(item); 94 | } 95 | }; 96 | 97 | /** 98 | * Clears all nodes and children from the QuadTree 99 | * @method clear 100 | **/ 101 | QuadTree.prototype.clear = function() 102 | { 103 | this.root.clear(); 104 | }; 105 | 106 | /** 107 | * Retrieves all items / points in the same node as the specified item / point. If the specified item 108 | * overlaps the bounds of a node, then all children in both nodes will be returned. 109 | * @method retrieve 110 | * @param {Object} item An object representing a 2D coordinate point (with x, y properties), or a shape 111 | * with dimensions (x, y, width, height) properties. 112 | **/ 113 | QuadTree.prototype.retrieve = function(item) 114 | { 115 | //get a copy of the array of items 116 | var out = this.root.retrieve(item).slice(0); 117 | //return QuadTree._filterResults(out, {x:item.x, y:item.y, width:0, height:0}); 118 | return out; 119 | }; 120 | 121 | QuadTree.prototype.retrieveInBounds = function (bounds) 122 | { 123 | var treeResult = this.root.retrieveInBounds(bounds); 124 | return QuadTree._filterResults(treeResult, bounds); 125 | }; 126 | 127 | QuadTree._filterResults = function(treeResult, bounds) 128 | { 129 | var filteredResult = []; 130 | 131 | if(this.root instanceof BoundsNode) 132 | { 133 | for (var i=0; i < treeResult.length; i++) 134 | { 135 | var node = treeResult[i]; 136 | if (QuadTree._isBoundOverlappingBound(node, bounds)) 137 | { 138 | filteredResult.push(node); 139 | } 140 | } 141 | } 142 | else 143 | { 144 | treeResult.forEach(function(node){ 145 | if(QuadTree._isPointInsideBounds(node, bounds)) 146 | { 147 | filteredResult.push(node); 148 | } 149 | }); 150 | } 151 | 152 | return filteredResult; 153 | }; 154 | 155 | QuadTree._isPointInsideBounds = function (point, bounds) 156 | { 157 | return ( 158 | (point.x >= bounds.x) && 159 | (point.x <= bounds.x + bounds.width) && 160 | (point.y >= bounds.y) && 161 | (point.y <= bounds.y + bounds.height) 162 | ); 163 | }; 164 | 165 | 166 | QuadTree._isBoundOverlappingBound = function (b1, b2) 167 | { 168 | return !( 169 | b1.x > (b2.x + b2.width) || 170 | b2.x > (b1.x + b1.width) || 171 | b1.y > (b2.y + b2.height) || 172 | b2.y > (b1.y + b1.height) 173 | ); 174 | }; 175 | 176 | /************** Node ********************/ 177 | 178 | 179 | function Node(bounds, depth, maxDepth, maxChildren) 180 | { 181 | this._bounds = bounds; 182 | this.children = []; 183 | this.nodes = []; 184 | 185 | if(maxChildren) 186 | { 187 | this._maxChildren = maxChildren; 188 | 189 | } 190 | 191 | if(maxDepth) 192 | { 193 | this._maxDepth = maxDepth; 194 | } 195 | 196 | if(depth) 197 | { 198 | this._depth = depth; 199 | } 200 | }; 201 | 202 | //subnodes 203 | Node.prototype.nodes = null; 204 | Node.prototype._classConstructor = Node; 205 | 206 | //children contained directly in the node 207 | Node.prototype.children = null; 208 | Node.prototype._bounds = null; 209 | 210 | //read only 211 | Node.prototype._depth = 0; 212 | 213 | Node.prototype._maxChildren = 4; 214 | Node.prototype._maxDepth = 4; 215 | 216 | Node.TOP_LEFT = 0; 217 | Node.TOP_RIGHT = 1; 218 | Node.BOTTOM_LEFT = 2; 219 | Node.BOTTOM_RIGHT = 3; 220 | 221 | 222 | Node.prototype.insert = function(item) 223 | { 224 | if(this.nodes.length) 225 | { 226 | var index = this._findIndex(item); 227 | 228 | this.nodes[index].insert(item); 229 | 230 | return; 231 | } 232 | 233 | this.children.push(item); 234 | 235 | var len = this.children.length; 236 | if(!(this._depth >= this._maxDepth) && 237 | len > this._maxChildren) 238 | { 239 | this.subdivide(); 240 | 241 | for(var i = 0; i < len; i++) 242 | { 243 | this.insert(this.children[i]); 244 | } 245 | 246 | this.children.length = 0; 247 | } 248 | }; 249 | 250 | Node.prototype.retrieve = function(item) 251 | { 252 | if(this.nodes.length) 253 | { 254 | var index = this._findIndex(item); 255 | 256 | return this.nodes[index].retrieve(item); 257 | } 258 | 259 | return this.children; 260 | }; 261 | 262 | Node.prototype.retrieveInBounds = function(bounds) 263 | { 264 | var result = []; 265 | 266 | if(this.collidesWith(bounds)) 267 | { 268 | result = result.concat(this._stuckChildren); 269 | 270 | if(this.children.length) 271 | { 272 | result = result.concat(this.children); 273 | } 274 | else 275 | { 276 | if(this.nodes.length) 277 | { 278 | for (var i = 0; i < this.nodes.length; i++) 279 | { 280 | result = result.concat(this.nodes[i].retrieveInBounds(bounds)); 281 | } 282 | } 283 | } 284 | } 285 | 286 | return result; 287 | }; 288 | 289 | 290 | Node.prototype.collidesWith = function (bounds) 291 | { 292 | var b1 = this._bounds; 293 | var b2 = bounds; 294 | 295 | return !( 296 | b1.x > (b2.x + b2.width) || 297 | b2.x > (b1.x + b1.width) || 298 | b1.y > (b2.y + b2.height) || 299 | b2.y > (b1.y + b1.height) 300 | ); 301 | }; 302 | 303 | Node.prototype._findIndex = function(item) 304 | { 305 | var b = this._bounds; 306 | var left = (item.x > b.x + b.width / 2)? false : true; 307 | var top = (item.y > b.y + b.height / 2)? false : true; 308 | 309 | //top left 310 | var index = Node.TOP_LEFT; 311 | if(left) 312 | { 313 | //left side 314 | if(!top) 315 | { 316 | //bottom left 317 | index = Node.BOTTOM_LEFT; 318 | } 319 | } 320 | else 321 | { 322 | //right side 323 | if(top) 324 | { 325 | //top right 326 | index = Node.TOP_RIGHT; 327 | } 328 | else 329 | { 330 | //bottom right 331 | index = Node.BOTTOM_RIGHT; 332 | } 333 | } 334 | 335 | return index; 336 | }; 337 | 338 | 339 | Node.prototype.subdivide = function() 340 | { 341 | var depth = this._depth + 1; 342 | 343 | var bx = this._bounds.x; 344 | var by = this._bounds.y; 345 | 346 | //floor the values 347 | var b_w_h = (this._bounds.width / 2)|0; 348 | var b_h_h = (this._bounds.height / 2)|0; 349 | var bx_b_w_h = bx + b_w_h; 350 | var by_b_h_h = by + b_h_h; 351 | 352 | //top left 353 | this.nodes[Node.TOP_LEFT] = new this._classConstructor({ 354 | x:bx, 355 | y:by, 356 | width:b_w_h, 357 | height:b_h_h 358 | }, 359 | depth, this._maxDepth, this._maxChildren); 360 | 361 | //top right 362 | this.nodes[Node.TOP_RIGHT] = new this._classConstructor({ 363 | x:bx_b_w_h, 364 | y:by, 365 | width:b_w_h, 366 | height:b_h_h 367 | }, 368 | depth, this._maxDepth, this._maxChildren); 369 | 370 | //bottom left 371 | this.nodes[Node.BOTTOM_LEFT] = new this._classConstructor({ 372 | x:bx, 373 | y:by_b_h_h, 374 | width:b_w_h, 375 | height:b_h_h 376 | }, 377 | depth, this._maxDepth, this._maxChildren); 378 | 379 | 380 | //bottom right 381 | this.nodes[Node.BOTTOM_RIGHT] = new this._classConstructor({ 382 | x:bx_b_w_h, 383 | y:by_b_h_h, 384 | width:b_w_h, 385 | height:b_h_h 386 | }, 387 | depth, this._maxDepth, this._maxChildren); 388 | }; 389 | 390 | Node.prototype.clear = function() 391 | { 392 | this.children.length = 0; 393 | 394 | var len = this.nodes.length; 395 | for(var i = 0; i < len; i++) 396 | { 397 | this.nodes[i].clear(); 398 | } 399 | 400 | this.nodes.length = 0; 401 | }; 402 | 403 | 404 | /******************** BoundsQuadTree ****************/ 405 | 406 | function BoundsNode(bounds, depth, maxChildren, maxDepth) 407 | { 408 | Node.call(this, bounds, depth, maxChildren, maxDepth); 409 | this._stuckChildren = []; 410 | } 411 | 412 | BoundsNode.prototype = new Node(); 413 | BoundsNode.prototype._classConstructor = BoundsNode; 414 | BoundsNode.prototype._stuckChildren = null; 415 | 416 | //we use this to collect and conctenate items being retrieved. This way 417 | //we dont have to continuously create new Array instances. 418 | //Note, when returned from QuadTree.retrieve, we then copy the array 419 | BoundsNode.prototype._out = []; 420 | 421 | BoundsNode.prototype.insert = function(item) 422 | { 423 | if(this.nodes.length) 424 | { 425 | var index = this._findIndex(item); 426 | var node = this.nodes[index]; 427 | 428 | //todo: make _bounds bounds 429 | if(item.x >= node._bounds.x && 430 | item.x + item.width <= node._bounds.x + node._bounds.width && 431 | item.y >= node._bounds.y && 432 | item.y + item.height <= node._bounds.y + node._bounds.height) 433 | { 434 | this.nodes[index].insert(item); 435 | } 436 | else 437 | { 438 | this._stuckChildren.push(item); 439 | } 440 | 441 | return; 442 | } 443 | 444 | this.children.push(item); 445 | 446 | var len = this.children.length; 447 | 448 | if(this._depth < this._maxDepth && 449 | len > this._maxChildren) 450 | { 451 | this.subdivide(); 452 | 453 | for(var i = 0; i < len; i++) 454 | { 455 | this.insert(this.children[i]); 456 | } 457 | 458 | this.children.length = 0; 459 | } 460 | }; 461 | 462 | BoundsNode.prototype.getChildren = function() 463 | { 464 | return this.children.concat(this._stuckChildren); 465 | }; 466 | 467 | BoundsNode.prototype.retrieve = function(item) 468 | { 469 | var out = this._out; 470 | out.length = 0; 471 | if(this.nodes.length) 472 | { 473 | var index = this._findIndex(item); 474 | 475 | out.push.apply(out, this.nodes[index].retrieve(item)); 476 | } 477 | 478 | out.push.apply(out, this._stuckChildren); 479 | out.push.apply(out, this.children); 480 | 481 | return out; 482 | }; 483 | 484 | BoundsNode.prototype.clear = function() 485 | { 486 | 487 | this._stuckChildren.length = 0; 488 | 489 | //array 490 | this.children.length = 0; 491 | 492 | var len = this.nodes.length; 493 | 494 | if(!len) 495 | { 496 | return; 497 | } 498 | 499 | for(var i = 0; i < len; i++) 500 | { 501 | this.nodes[i].clear(); 502 | } 503 | 504 | //array 505 | this.nodes.length = 0; 506 | 507 | //we could call the super clear function but for now, im just going to inline it 508 | //call the hidden super.clear, and make sure its called with this = this instance 509 | //Object.getPrototypeOf(BoundsNode.prototype).clear.call(this); 510 | }; 511 | 512 | //BoundsNode.prototype.getChildCount 513 | 514 | window.QuadTree = QuadTree; 515 | 516 | /* 517 | //http://ejohn.org/blog/objectgetprototypeof/ 518 | if ( typeof Object.getPrototypeOf !== "function" ) { 519 | if ( typeof "test".__proto__ === "object" ) { 520 | Object.getPrototypeOf = function(object){ 521 | return object.__proto__; 522 | }; 523 | } else { 524 | Object.getPrototypeOf = function(object){ 525 | // May break if the constructor has been tampered with 526 | return object.constructor.prototype; 527 | }; 528 | } 529 | } 530 | */ 531 | 532 | }(this)); -------------------------------------------------------------------------------- /js/heatmap-leaflet.js: -------------------------------------------------------------------------------- 1 | /* 2 | * heatmap.js 0.2 Leaflet overlay 3 | * 4 | * Copyright (c) 2012, Dominik Moritz 5 | * Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 6 | * and the Beerware (http://en.wikipedia.org/wiki/Beerware) license. 7 | * 8 | * Attribution 9 | * - Some snippets for canvas layer: https://gist.github.com/2566567 10 | * - QuadTree: https://github.com/jsmarkus/ExamplesByMesh/tree/master/JavaScript/QuadTree 11 | */ 12 | 13 | L.TileLayer.HeatMap = L.TileLayer.Canvas.extend({ 14 | options: { 15 | debug: false, 16 | opacity: 0.9, // opactity is between 0 and 1, not in percent 17 | radius: { 18 | value: 20, 19 | absolute: false // true: radius in meters, false: radius in pixels 20 | } 21 | }, 22 | 23 | initialize: function (options, data) { 24 | var self = this; 25 | L.Util.setOptions(this, options); 26 | 27 | this.drawTile = function (tile, tilePoint, zoom) { 28 | var ctx = { 29 | canvas: tile, 30 | tilePoint: tilePoint, 31 | zoom: zoom 32 | }; 33 | 34 | if (self.options.debug) { 35 | self._drawDebugInfo(ctx); 36 | } 37 | this._draw(ctx); 38 | }; 39 | }, 40 | 41 | _drawDebugInfo: function (ctx) { 42 | var canvas = L.DomUtil.create('canvas', 'leaflet-tile-debug'); 43 | var tileSize = this.options.tileSize; 44 | canvas.width = tileSize; 45 | canvas.height = tileSize; 46 | ctx.canvas.appendChild(canvas); 47 | ctx.dbgcanvas = canvas; 48 | 49 | var max = tileSize; 50 | var g = canvas.getContext('2d'); 51 | g.strokeStyle = '#000000'; 52 | g.fillStyle = '#FFFF00'; 53 | g.strokeRect(0, 0, max, max); 54 | g.font = "12px Arial"; 55 | g.fillRect(0, 0, 5, 5); 56 | g.fillRect(0, max - 5, 5, 5); 57 | g.fillRect(max - 5, 0, 5, 5); 58 | g.fillRect(max - 5, max - 5, 5, 5); 59 | g.fillRect(max / 2 - 5, max / 2 - 5, 10, 10); 60 | g.strokeText(ctx.tilePoint.x + ' ' + ctx.tilePoint.y + ' ' + ctx.zoom, max / 2 - 30, max / 2 - 10); 61 | 62 | this._drawPoint(ctx, [0,0]); 63 | }, 64 | 65 | /* 66 | * Used for debug 67 | */ 68 | _drawPoint: function (ctx, geom) { 69 | var p = this._tilePoint(ctx, geom); 70 | var c = ctx.dbgcanvas; 71 | var g = c.getContext('2d'); 72 | g.beginPath(); 73 | g.fillStyle = '#FF0000'; 74 | g.arc(p.x, p.y, 4, 0, Math.PI * 2); 75 | g.closePath(); 76 | g.fill(); 77 | g.restore(); 78 | }, 79 | 80 | _createTileProto: function () { 81 | var proto = this._canvasProto = L.DomUtil.create('div', 'leaflet-tile'); 82 | 83 | var tileSize = this.options.tileSize; 84 | proto.style.width = tileSize+"px"; 85 | proto.style.height = tileSize+"px"; 86 | proto.width = tileSize; 87 | proto.height = tileSize; 88 | }, 89 | 90 | /** 91 | * Inserts data into quadtree and redraws heatmap canvas 92 | */ 93 | setData: function(dataset) { 94 | var self = this; 95 | var latLngs = []; 96 | this._maxValue = 0; 97 | dataset.forEach(function(d) { 98 | latLngs.push(new L.LatLng(d.lat, d.lon)); 99 | self._maxValue = Math.max(self._maxValue, d.value); 100 | }); 101 | this._bounds = new L.LatLngBounds(latLngs); 102 | 103 | this._quad = new QuadTree(this._boundsToQuery(this._bounds), false, 6, 6); 104 | 105 | dataset.forEach(function(d) { 106 | self._quad.insert({ 107 | x: d.lon, 108 | y: d.lat, 109 | value: d.value 110 | }); 111 | }); 112 | this.redraw(); 113 | }, 114 | 115 | /** 116 | * Transforms coordinates to tile space 117 | */ 118 | _tilePoint: function (ctx, coords) { 119 | // start coords to tile 'space' 120 | var s = ctx.tilePoint.multiplyBy(this.options.tileSize); 121 | 122 | // actual coords to tile 'space' 123 | var p = this._map.project(new L.LatLng(coords[1], coords[0])); 124 | 125 | // point to draw 126 | var x = Math.round(p.x - s.x); 127 | var y = Math.round(p.y - s.y); 128 | return [x, y]; 129 | }, 130 | 131 | /** 132 | * Creates a query for the quadtree from bounds 133 | */ 134 | _boundsToQuery: function(bounds) { 135 | return { 136 | x: bounds.getSouthWest().lng, 137 | y: bounds.getSouthWest().lat, 138 | width: bounds.getNorthEast().lng-bounds.getSouthWest().lng, 139 | height: bounds.getNorthEast().lat-bounds.getSouthWest().lat 140 | }; 141 | }, 142 | 143 | _getLatRadius: function () { 144 | return (this.options.radius.value / 40075017) * 360; 145 | }, 146 | 147 | _getLngRadius: function (point) { 148 | return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * point.lat); 149 | }, 150 | 151 | /* 152 | * The idea is to create two points and then get 153 | * the distance between the two in order to know what 154 | * the absolute radius in this tile could be. 155 | */ 156 | projectLatlngs: function (point) { 157 | var lngRadius = this._getLngRadius(point), 158 | latlng2 = new L.LatLng(point.lat, point.lng - lngRadius, true), 159 | p = this._map.latLngToLayerPoint(latlng2), 160 | q = this._map.latLngToLayerPoint(point); 161 | return Math.max(Math.round(q.x - p.x), 1); 162 | }, 163 | 164 | _draw: function (ctx) { 165 | if (!this._quad || !this._map) { 166 | return; 167 | } 168 | 169 | var self = this, 170 | options = this.options, 171 | tile = ctx.canvas, 172 | tileSize = options.tileSize, 173 | radiusValue = this.options.radius.value; 174 | 175 | var localXY, value, pointsInTile = []; 176 | 177 | var nwPoint = ctx.tilePoint.multiplyBy(tileSize), 178 | sePoint = nwPoint.add(new L.Point(tileSize, tileSize)); 179 | 180 | // Set the radius for the tile, if necessary. 181 | // The radius of a circle can be either absolute in pixels or in meters 182 | // The radius in pixels is not the same on the whole map. 183 | if (options.radius.absolute) { 184 | var centerPoint = nwPoint.add(new L.Point(tileSize/2, tileSize/2)); 185 | var p = this._map.unproject(centerPoint); 186 | radiusValue = this.projectLatlngs(p); 187 | } 188 | 189 | var heatmap = h337.create({ 190 | "radius": radiusValue, 191 | "element": tile, 192 | "visible": true, 193 | "opacity": 100, // we use leaflet's opacity for tiles 194 | "gradient": options.gradient, 195 | "debug": options.debug 196 | }); 197 | 198 | // padding 199 | var pad = new L.Point(radiusValue, radiusValue); 200 | nwPoint = nwPoint.subtract(pad); 201 | sePoint = sePoint.add(pad); 202 | 203 | var bounds = new L.LatLngBounds(this._map.unproject(sePoint), this._map.unproject(nwPoint)); 204 | this._quad.retrieveInBounds(this._boundsToQuery(bounds)).forEach(function(obj) { 205 | localXY = self._tilePoint(ctx, [obj.x, obj.y]); 206 | value = obj.value; 207 | pointsInTile.push({ 208 | x: localXY[0], 209 | y: localXY[1], 210 | count: value 211 | }); 212 | }); 213 | 214 | heatmap.store.setDataSet({max: this._maxValue, data: pointsInTile}); 215 | 216 | return this; 217 | } 218 | }); 219 | 220 | L.TileLayer.heatMap = function (options) { 221 | return new L.TileLayer.HeatMap(options); 222 | }; -------------------------------------------------------------------------------- /js/heatmap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * heatmap.js 1.0 - JavaScript Heatmap Library 3 | * 4 | * Copyright (c) 2011, Patrick Wied (http://www.patrick-wied.at) 5 | * Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 6 | * and the Beerware (http://en.wikipedia.org/wiki/Beerware) license. 7 | */ 8 | 9 | (function(w){ 10 | // the heatmapFactory creates heatmap instances 11 | var heatmapFactory = (function(){ 12 | 13 | // store object constructor 14 | // a heatmap contains a store 15 | // the store has to know about the heatmap in order to trigger heatmap updates when datapoints get added 16 | var store = function store(hmap){ 17 | 18 | var _ = { 19 | // data is a two dimensional array 20 | // a datapoint gets saved as data[point-x-value][point-y-value] 21 | // the value at [point-x-value][point-y-value] is the occurrence of the datapoint 22 | data: [], 23 | // tight coupling of the heatmap object 24 | heatmap: hmap 25 | }; 26 | // the max occurrence - the heatmaps radial gradient alpha transition is based on it 27 | this.max = 1; 28 | 29 | this.get = function(key){ 30 | return _[key]; 31 | }; 32 | this.set = function(key, value){ 33 | _[key] = value; 34 | }; 35 | } 36 | 37 | store.prototype = { 38 | // function for adding datapoints to the store 39 | // datapoints are usually defined by x and y but could also contain a third parameter which represents the occurrence 40 | addDataPoint: function(x, y){ 41 | if(x < 0 || y < 0) 42 | return; 43 | 44 | var me = this, 45 | heatmap = me.get("heatmap"), 46 | data = me.get("data"); 47 | 48 | if(!data[x]) 49 | data[x] = []; 50 | 51 | if(!data[x][y]) 52 | data[x][y] = 0; 53 | 54 | // if count parameter is set increment by count otherwise by 1 55 | data[x][y]+=(arguments.length<3)?1:arguments[2]; 56 | 57 | me.set("data", data); 58 | // do we have a new maximum? 59 | if(me.max < data[x][y]){ 60 | // max changed, we need to redraw all existing(lower) datapoints 61 | heatmap.get("actx").clearRect(0,0,heatmap.get("width"),heatmap.get("height")); 62 | me.setDataSet({ max: data[x][y], data: data }, true); 63 | return; 64 | } 65 | heatmap.drawAlpha(x, y, data[x][y], true); 66 | }, 67 | setDataSet: function(obj, internal){ 68 | var me = this, 69 | heatmap = me.get("heatmap"), 70 | data = [], 71 | d = obj.data, 72 | dlen = d.length; 73 | // clear the heatmap before the data set gets drawn 74 | heatmap.clear(); 75 | this.max = obj.max; 76 | // if a legend is set, update it 77 | heatmap.get("legend") && heatmap.get("legend").update(obj.max); 78 | 79 | if(internal != null && internal){ 80 | for(var one in d){ 81 | // jump over undefined indexes 82 | if(one === undefined) 83 | continue; 84 | for(var two in d[one]){ 85 | if(two === undefined) 86 | continue; 87 | // if both indexes are defined, push the values into the array 88 | heatmap.drawAlpha(one, two, d[one][two], false); 89 | } 90 | } 91 | }else{ 92 | while(dlen--){ 93 | var point = d[dlen]; 94 | heatmap.drawAlpha(point.x, point.y, point.count, false); 95 | if(!data[point.x]) 96 | data[point.x] = []; 97 | 98 | if(!data[point.x][point.y]) 99 | data[point.x][point.y] = 0; 100 | 101 | data[point.x][point.y] = point.count; 102 | } 103 | } 104 | heatmap.colorize(); 105 | this.set("data", d); 106 | }, 107 | exportDataSet: function(){ 108 | var me = this, 109 | data = me.get("data"), 110 | exportData = []; 111 | 112 | for(var one in data){ 113 | // jump over undefined indexes 114 | if(one === undefined) 115 | continue; 116 | for(var two in data[one]){ 117 | if(two === undefined) 118 | continue; 119 | // if both indexes are defined, push the values into the array 120 | exportData.push({x: parseInt(one, 10), y: parseInt(two, 10), count: data[one][two]}); 121 | } 122 | } 123 | 124 | return { max: me.max, data: exportData }; 125 | }, 126 | generateRandomDataSet: function(points){ 127 | var heatmap = this.get("heatmap"), 128 | w = heatmap.get("width"), 129 | h = heatmap.get("height"); 130 | var randomset = {}, 131 | max = Math.floor(Math.random()*1000+1); 132 | randomset.max = max; 133 | var data = []; 134 | while(points--){ 135 | data.push({x: Math.floor(Math.random()*w+1), y: Math.floor(Math.random()*h+1), count: Math.floor(Math.random()*max+1)}); 136 | } 137 | randomset.data = data; 138 | this.setDataSet(randomset); 139 | } 140 | }; 141 | 142 | var legend = function legend(config){ 143 | this.config = config; 144 | 145 | var _ = { 146 | element: null, 147 | labelsEl: null, 148 | gradientCfg: null, 149 | ctx: null 150 | }; 151 | this.get = function(key){ 152 | return _[key]; 153 | }; 154 | this.set = function(key, value){ 155 | _[key] = value; 156 | }; 157 | this.init(); 158 | }; 159 | legend.prototype = { 160 | init: function(){ 161 | var me = this, 162 | config = me.config, 163 | title = config.title || "Legend", 164 | position = config.position, 165 | offset = config.offset || 10, 166 | gconfig = config.gradient, 167 | labelsEl = document.createElement("ul"), 168 | labelsHtml = "", 169 | grad, element, gradient, positionCss = ""; 170 | 171 | me.processGradientObject(); 172 | 173 | // Positioning 174 | 175 | // top or bottom 176 | if(position.indexOf('t') > -1){ 177 | positionCss += 'top:'+offset+'px;'; 178 | }else{ 179 | positionCss += 'bottom:'+offset+'px;'; 180 | } 181 | 182 | // left or right 183 | if(position.indexOf('l') > -1){ 184 | positionCss += 'left:'+offset+'px;'; 185 | }else{ 186 | positionCss += 'right:'+offset+'px;'; 187 | } 188 | 189 | element = document.createElement("div"); 190 | element.style.cssText = "border-radius:5px;position:absolute;"+positionCss+"font-family:Helvetica; width:256px;z-index:10000000000; background:rgba(255,255,255,1);padding:10px;border:1px solid black;margin:0;"; 191 | element.innerHTML = "

"+title+"

"; 192 | // create gradient in canvas 193 | labelsEl.style.cssText = "position:relative;font-size:12px;display:block;list-style:none;list-style-type:none;margin:0;height:15px;"; 194 | 195 | 196 | // create gradient element 197 | gradient = document.createElement("div"); 198 | gradient.style.cssText = ["position:relative;display:block;width:256px;height:15px;border-bottom:1px solid black; background-image:url(",me.createGradientImage(),");"].join(""); 199 | 200 | element.appendChild(labelsEl); 201 | element.appendChild(gradient); 202 | 203 | me.set("element", element); 204 | me.set("labelsEl", labelsEl); 205 | 206 | me.update(1); 207 | }, 208 | processGradientObject: function(){ 209 | // create array and sort it 210 | var me = this, 211 | gradientConfig = this.config.gradient, 212 | gradientArr = []; 213 | 214 | for(var key in gradientConfig){ 215 | if(gradientConfig.hasOwnProperty(key)){ 216 | gradientArr.push({ stop: key, value: gradientConfig[key] }); 217 | } 218 | } 219 | gradientArr.sort(function(a, b){ 220 | return (a.stop - b.stop); 221 | }); 222 | gradientArr.unshift({ stop: 0, value: 'rgba(0,0,0,0)' }); 223 | 224 | me.set("gradientArr", gradientArr); 225 | }, 226 | createGradientImage: function(){ 227 | var me = this, 228 | gradArr = me.get("gradientArr"), 229 | length = gradArr.length, 230 | canvas = document.createElement("canvas"), 231 | ctx = canvas.getContext("2d"), 232 | grad; 233 | // the gradient in the legend including the ticks will be 256x15px 234 | canvas.width = "256"; 235 | canvas.height = "15"; 236 | 237 | grad = ctx.createLinearGradient(0,5,256,10); 238 | 239 | for(var i = 0; i < length; i++){ 240 | grad.addColorStop(1/(length-1) * i, gradArr[i].value); 241 | } 242 | 243 | ctx.fillStyle = grad; 244 | ctx.fillRect(0,5,256,10); 245 | ctx.strokeStyle = "black"; 246 | ctx.beginPath(); 247 | 248 | for(var i = 0; i < length; i++){ 249 | ctx.moveTo(((1/(length-1)*i*256) >> 0)+.5, 0); 250 | ctx.lineTo(((1/(length-1)*i*256) >> 0)+.5, (i==0)?15:5); 251 | } 252 | ctx.moveTo(255.5, 0); 253 | ctx.lineTo(255.5, 15); 254 | ctx.moveTo(255.5, 4.5); 255 | ctx.lineTo(0, 4.5); 256 | 257 | ctx.stroke(); 258 | 259 | // we re-use the context for measuring the legends label widths 260 | me.set("ctx", ctx); 261 | 262 | return canvas.toDataURL(); 263 | }, 264 | getElement: function(){ 265 | return this.get("element"); 266 | }, 267 | update: function(max){ 268 | var me = this, 269 | gradient = me.get("gradientArr"), 270 | ctx = me.get("ctx"), 271 | labels = me.get("labelsEl"), 272 | labelText, labelsHtml = "", offset; 273 | 274 | for(var i = 0; i < gradient.length; i++){ 275 | 276 | labelText = max*gradient[i].stop >> 0; 277 | offset = (ctx.measureText(labelText).width/2) >> 0; 278 | 279 | if(i == 0){ 280 | offset = 0; 281 | } 282 | if(i == gradient.length-1){ 283 | offset *= 2; 284 | } 285 | labelsHtml += '
  • '+labelText+'
  • '; 286 | } 287 | labels.innerHTML = labelsHtml; 288 | } 289 | }; 290 | 291 | // heatmap object constructor 292 | var heatmap = function heatmap(config){ 293 | // private variables 294 | var _ = { 295 | radius : 40, 296 | element : {}, 297 | canvas : {}, 298 | acanvas: {}, 299 | ctx : {}, 300 | actx : {}, 301 | legend: null, 302 | visible : true, 303 | width : 0, 304 | height : 0, 305 | max : false, 306 | gradient : false, 307 | opacity: 180, 308 | premultiplyAlpha: false, 309 | bounds: { 310 | l: 1000, 311 | r: 0, 312 | t: 1000, 313 | b: 0 314 | }, 315 | debug: false 316 | }; 317 | // heatmap store containing the datapoints and information about the maximum 318 | // accessible via instance.store 319 | this.store = new store(this); 320 | 321 | this.get = function(key){ 322 | return _[key]; 323 | }; 324 | this.set = function(key, value){ 325 | _[key] = value; 326 | }; 327 | // configure the heatmap when an instance gets created 328 | this.configure(config); 329 | // and initialize it 330 | this.init(); 331 | }; 332 | 333 | // public functions 334 | heatmap.prototype = { 335 | configure: function(config){ 336 | var me = this, 337 | rout, rin; 338 | 339 | me.set("radius", config["radius"] || 40); 340 | me.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element)); 341 | me.set("visible", (config.visible != null)?config.visible:true); 342 | me.set("max", config.max || false); 343 | me.set("gradient", config.gradient || { 0.45: "rgb(0,0,255)", 0.55: "rgb(0,255,255)", 0.65: "rgb(0,255,0)", 0.95: "yellow", 1.0: "rgb(255,0,0)"}); // default is the common blue to red gradient 344 | me.set("opacity", parseInt(255/(100/config.opacity), 10) || 180); 345 | me.set("width", config.width || 0); 346 | me.set("height", config.height || 0); 347 | me.set("debug", config.debug); 348 | 349 | if(config.legend){ 350 | var legendCfg = config.legend; 351 | legendCfg.gradient = me.get("gradient"); 352 | me.set("legend", new legend(legendCfg)); 353 | } 354 | 355 | }, 356 | resize: function () { 357 | var me = this, 358 | element = me.get("element"), 359 | canvas = me.get("canvas"), 360 | acanvas = me.get("acanvas"); 361 | canvas.width = acanvas.width = me.get("width") || element.style.width.replace(/px/, "") || me.getWidth(element); 362 | this.set("width", canvas.width); 363 | canvas.height = acanvas.height = me.get("height") || element.style.height.replace(/px/, "") || me.getHeight(element); 364 | this.set("height", canvas.height); 365 | }, 366 | 367 | init: function(){ 368 | var me = this, 369 | canvas = document.createElement("canvas"), 370 | acanvas = document.createElement("canvas"), 371 | ctx = canvas.getContext("2d"), 372 | actx = acanvas.getContext("2d"), 373 | element = me.get("element"); 374 | 375 | 376 | me.initColorPalette(); 377 | 378 | me.set("canvas", canvas); 379 | me.set("ctx", ctx); 380 | me.set("acanvas", acanvas); 381 | me.set("actx", actx); 382 | 383 | me.resize(); 384 | canvas.style.cssText = acanvas.style.cssText = "position:absolute;top:0;left:0;z-index:10000000;"; 385 | 386 | if(!me.get("visible")) 387 | canvas.style.display = "none"; 388 | 389 | element.appendChild(canvas); 390 | if(me.get("legend")){ 391 | element.appendChild(me.get("legend").getElement()); 392 | } 393 | 394 | // debugging purposes only 395 | if(me.get("debug")) 396 | document.body.appendChild(acanvas); 397 | 398 | actx.shadowOffsetX = 15000; 399 | actx.shadowOffsetY = 15000; 400 | actx.shadowBlur = 15; 401 | }, 402 | initColorPalette: function(){ 403 | 404 | var me = this, 405 | canvas = document.createElement("canvas"), 406 | gradient = me.get("gradient"), 407 | ctx, grad, testData; 408 | 409 | canvas.width = "1"; 410 | canvas.height = "256"; 411 | ctx = canvas.getContext("2d"); 412 | grad = ctx.createLinearGradient(0,0,1,256); 413 | 414 | // Test how the browser renders alpha by setting a partially transparent pixel 415 | // and reading the result. A good browser will return a value reasonably close 416 | // to what was set. Some browsers (e.g. on Android) will return a ridiculously wrong value. 417 | testData = ctx.getImageData(0,0,1,1); 418 | testData.data[0] = testData.data[3] = 64; // 25% red & alpha 419 | testData.data[1] = testData.data[2] = 0; // 0% blue & green 420 | ctx.putImageData(testData, 0, 0); 421 | testData = ctx.getImageData(0,0,1,1); 422 | me.set("premultiplyAlpha", (testData.data[0] < 60 || testData.data[0] > 70)); 423 | 424 | for(var x in gradient){ 425 | grad.addColorStop(x, gradient[x]); 426 | } 427 | 428 | ctx.fillStyle = grad; 429 | ctx.fillRect(0,0,1,256); 430 | 431 | me.set("gradient", ctx.getImageData(0,0,1,256).data); 432 | }, 433 | getWidth: function(element){ 434 | var width = element.offsetWidth; 435 | if(element.style.paddingLeft){ 436 | width+=element.style.paddingLeft; 437 | } 438 | if(element.style.paddingRight){ 439 | width+=element.style.paddingRight; 440 | } 441 | 442 | return width; 443 | }, 444 | getHeight: function(element){ 445 | var height = element.offsetHeight; 446 | if(element.style.paddingTop){ 447 | height+=element.style.paddingTop; 448 | } 449 | if(element.style.paddingBottom){ 450 | height+=element.style.paddingBottom; 451 | } 452 | 453 | return height; 454 | }, 455 | colorize: function(x, y){ 456 | // get the private variables 457 | var me = this, 458 | width = me.get("width"), 459 | radius = me.get("radius"), 460 | height = me.get("height"), 461 | actx = me.get("actx"), 462 | ctx = me.get("ctx"), 463 | x2 = radius * 3, 464 | premultiplyAlpha = me.get("premultiplyAlpha"), 465 | palette = me.get("gradient"), 466 | opacity = me.get("opacity"), 467 | bounds = me.get("bounds"), 468 | left, top, bottom, right, 469 | image, imageData, length, alpha, offset, finalAlpha; 470 | 471 | if(x != null && y != null){ 472 | if(x+x2>width){ 473 | x=width-x2; 474 | } 475 | if(x<0){ 476 | x=0; 477 | } 478 | if(y<0){ 479 | y=0; 480 | } 481 | if(y+x2>height){ 482 | y=height-x2; 483 | } 484 | left = x; 485 | top = y; 486 | right = x + x2; 487 | bottom = y + x2; 488 | 489 | }else{ 490 | if(bounds['l'] < 0){ 491 | left = 0; 492 | }else{ 493 | left = bounds['l']; 494 | } 495 | if(bounds['r'] > width){ 496 | right = width; 497 | }else{ 498 | right = bounds['r']; 499 | } 500 | if(bounds['t'] < 0){ 501 | top = 0; 502 | }else{ 503 | top = bounds['t']; 504 | } 505 | if(bounds['b'] > height){ 506 | bottom = height; 507 | }else{ 508 | bottom = bounds['b']; 509 | } 510 | } 511 | 512 | image = actx.getImageData(left, top, right-left, bottom-top); 513 | imageData = image.data; 514 | length = imageData.length; 515 | // loop thru the area 516 | for(var i=3; i < length; i+=4){ 517 | 518 | // [0] -> r, [1] -> g, [2] -> b, [3] -> alpha 519 | alpha = imageData[i], 520 | offset = alpha*4; 521 | 522 | if(!offset) 523 | continue; 524 | 525 | // we ve started with i=3 526 | // set the new r, g and b values 527 | finalAlpha = (alpha < opacity)?alpha:opacity; 528 | imageData[i-3]=palette[offset]; 529 | imageData[i-2]=palette[offset+1]; 530 | imageData[i-1]=palette[offset+2]; 531 | 532 | if (premultiplyAlpha) { 533 | // To fix browsers that premultiply incorrectly, we'll pass in a value scaled 534 | // appropriately so when the multiplication happens the correct value will result. 535 | imageData[i-3] /= 255/finalAlpha; 536 | imageData[i-2] /= 255/finalAlpha; 537 | imageData[i-1] /= 255/finalAlpha; 538 | } 539 | 540 | // we want the heatmap to have a gradient from transparent to the colors 541 | // as long as alpha is lower than the defined opacity (maximum), we'll use the alpha value 542 | imageData[i] = finalAlpha; 543 | } 544 | // the rgb data manipulation didn't affect the ImageData object(defined on the top) 545 | // after the manipulation process we have to set the manipulated data to the ImageData object 546 | image.data = imageData; 547 | ctx.putImageData(image, left, top); 548 | }, 549 | drawAlpha: function(x, y, count, colorize){ 550 | // storing the variables because they will be often used 551 | var me = this, 552 | radius = me.get("radius"), 553 | ctx = me.get("actx"), 554 | max = me.get("max"), 555 | bounds = me.get("bounds"), 556 | xb = x - (1.5 * radius) >> 0, yb = y - (1.5 * radius) >> 0, 557 | xc = x + (1.5 * radius) >> 0, yc = y + (1.5 * radius) >> 0; 558 | 559 | ctx.shadowColor = ('rgba(0,0,0,'+((count)?(count/me.store.max):'0.1')+')'); 560 | 561 | ctx.shadowOffsetX = 15000; 562 | ctx.shadowOffsetY = 15000; 563 | ctx.shadowBlur = 15; 564 | 565 | ctx.beginPath(); 566 | ctx.arc(x - 15000, y - 15000, radius, 0, Math.PI * 2, true); 567 | ctx.closePath(); 568 | ctx.fill(); 569 | if(colorize){ 570 | // finally colorize the area 571 | me.colorize(xb,yb); 572 | }else{ 573 | // or update the boundaries for the area that then should be colorized 574 | if(xb < bounds["l"]){ 575 | bounds["l"] = xb; 576 | } 577 | if(yb < bounds["t"]){ 578 | bounds["t"] = yb; 579 | } 580 | if(xc > bounds['r']){ 581 | bounds['r'] = xc; 582 | } 583 | if(yc > bounds['b']){ 584 | bounds['b'] = yc; 585 | } 586 | } 587 | }, 588 | toggleDisplay: function(){ 589 | var me = this, 590 | visible = me.get("visible"), 591 | canvas = me.get("canvas"); 592 | 593 | if(!visible) 594 | canvas.style.display = "block"; 595 | else 596 | canvas.style.display = "none"; 597 | 598 | me.set("visible", !visible); 599 | }, 600 | // dataURL export 601 | getImageData: function(){ 602 | return this.get("canvas").toDataURL(); 603 | }, 604 | clear: function(){ 605 | var me = this, 606 | w = me.get("width"), 607 | h = me.get("height"); 608 | 609 | me.store.set("data",[]); 610 | // @TODO: reset stores max to 1 611 | //me.store.max = 1; 612 | me.get("ctx").clearRect(0,0,w,h); 613 | me.get("actx").clearRect(0,0,w,h); 614 | }, 615 | cleanup: function(){ 616 | var me = this; 617 | me.get("element").removeChild(me.get("canvas")); 618 | } 619 | }; 620 | 621 | return { 622 | create: function(config){ 623 | return new heatmap(config); 624 | }, 625 | util: { 626 | mousePosition: function(ev){ 627 | // this doesn't work right 628 | // rather use 629 | /* 630 | // this = element to observe 631 | var x = ev.pageX - this.offsetLeft; 632 | var y = ev.pageY - this.offsetTop; 633 | 634 | */ 635 | var x, y; 636 | 637 | if (ev.layerX) { // Firefox 638 | x = ev.layerX; 639 | y = ev.layerY; 640 | } else if (ev.offsetX) { // Opera 641 | x = ev.offsetX; 642 | y = ev.offsetY; 643 | } 644 | if(typeof(x)=='undefined') 645 | return; 646 | 647 | return [x,y]; 648 | } 649 | } 650 | }; 651 | })(); 652 | w.h337 = w.heatmapFactory = heatmapFactory; 653 | })(window); 654 | -------------------------------------------------------------------------------- /js/leaflet-providers.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module. 4 | define(['leaflet'], factory); 5 | } else { 6 | // Assume leaflet is loaded into global object L already 7 | factory(L); 8 | } 9 | }(this, function (L) { 10 | 'use strict'; 11 | 12 | L.TileLayer.Provider = L.TileLayer.extend({ 13 | initialize: function (arg, options) { 14 | var providers = L.TileLayer.Provider.providers; 15 | 16 | var parts = arg.split('.'); 17 | 18 | var providerName = parts[0]; 19 | var variantName = parts[1]; 20 | 21 | if (!providers[providerName]) { 22 | throw 'No such provider (' + providerName + ')'; 23 | } 24 | 25 | var provider = { 26 | url: providers[providerName].url, 27 | options: providers[providerName].options 28 | }; 29 | 30 | // overwrite values in provider from variant. 31 | if (variantName && 'variants' in providers[providerName]) { 32 | if (!(variantName in providers[providerName].variants)) { 33 | throw 'No such variant of ' + providerName + ' (' + variantName + ')'; 34 | } 35 | var variant = providers[providerName].variants[variantName]; 36 | var variantOptions; 37 | if (typeof variant === 'string') { 38 | variantOptions = { 39 | variant: variant 40 | }; 41 | } else { 42 | variantOptions = variant.options; 43 | } 44 | provider = { 45 | url: variant.url || provider.url, 46 | options: L.Util.extend({}, provider.options, variantOptions) 47 | }; 48 | } else if (typeof provider.url === 'function') { 49 | provider.url = provider.url(parts.splice(1, parts.length - 1).join('.')); 50 | } 51 | 52 | var forceHTTP = window.location.protocol === 'file:' || provider.options.forceHTTP; 53 | if (provider.url.indexOf('//') === 0 && forceHTTP) { 54 | provider.url = 'http:' + provider.url; 55 | } 56 | 57 | // replace attribution placeholders with their values from toplevel provider attribution, 58 | // recursively 59 | var attributionReplacer = function (attr) { 60 | if (attr.indexOf('{attribution.') === -1) { 61 | return attr; 62 | } 63 | return attr.replace(/\{attribution.(\w*)\}/, 64 | function (match, attributionName) { 65 | return attributionReplacer(providers[attributionName].options.attribution); 66 | } 67 | ); 68 | }; 69 | provider.options.attribution = attributionReplacer(provider.options.attribution); 70 | 71 | // Compute final options combining provider options with any user overrides 72 | var layerOpts = L.Util.extend({}, provider.options, options); 73 | L.TileLayer.prototype.initialize.call(this, provider.url, layerOpts); 74 | } 75 | }); 76 | 77 | /** 78 | * Definition of providers. 79 | * see http://leafletjs.com/reference.html#tilelayer for options in the options map. 80 | */ 81 | 82 | L.TileLayer.Provider.providers = { 83 | OpenStreetMap: { 84 | url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 85 | options: { 86 | maxZoom: 19, 87 | attribution: 88 | '© OpenStreetMap' 89 | }, 90 | variants: { 91 | Mapnik: {}, 92 | BlackAndWhite: { 93 | url: 'http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', 94 | options: { 95 | maxZoom: 18 96 | } 97 | }, 98 | DE: { 99 | url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', 100 | options: { 101 | maxZoom: 18 102 | } 103 | }, 104 | France: { 105 | url: 'http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', 106 | options: { 107 | attribution: '© Openstreetmap France | {attribution.OpenStreetMap}' 108 | } 109 | }, 110 | HOT: { 111 | url: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', 112 | options: { 113 | attribution: '{attribution.OpenStreetMap}, Tiles courtesy of Humanitarian OpenStreetMap Team' 114 | } 115 | } 116 | } 117 | }, 118 | OpenSeaMap: { 119 | url: 'http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 120 | options: { 121 | attribution: 'Map data: © OpenSeaMap contributors' 122 | } 123 | }, 124 | OpenTopoMap: { 125 | url: '//{s}.tile.opentopomap.org/{z}/{x}/{y}.png', 126 | options: { 127 | maxZoom: 16, 128 | attribution: 'Map data: {attribution.OpenStreetMap}, SRTM | Map style: © OpenTopoMap (CC-BY-SA)' 129 | } 130 | }, 131 | Thunderforest: { 132 | url: '//{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png', 133 | options: { 134 | attribution: 135 | '© OpenCycleMap, {attribution.OpenStreetMap}', 136 | variant: 'cycle' 137 | }, 138 | variants: { 139 | OpenCycleMap: 'cycle', 140 | Transport: { 141 | options: { 142 | variant: 'transport', 143 | maxZoom: 19 144 | } 145 | }, 146 | TransportDark: { 147 | options: { 148 | variant: 'transport-dark', 149 | maxZoom: 19 150 | } 151 | }, 152 | Landscape: 'landscape', 153 | Outdoors: 'outdoors' 154 | } 155 | }, 156 | OpenMapSurfer: { 157 | url: 'http://openmapsurfer.uni-hd.de/tiles/{variant}/x={x}&y={y}&z={z}', 158 | options: { 159 | maxZoom: 20, 160 | variant: 'roads', 161 | attribution: 'Imagery from GIScience Research Group @ University of Heidelberg — Map data {attribution.OpenStreetMap}' 162 | }, 163 | variants: { 164 | Roads: 'roads', 165 | AdminBounds: { 166 | options: { 167 | variant: 'adminb', 168 | maxZoom: 19 169 | } 170 | }, 171 | Grayscale: { 172 | options: { 173 | variant: 'roadsg', 174 | maxZoom: 19 175 | } 176 | } 177 | } 178 | }, 179 | Hydda: { 180 | url: 'http://{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png', 181 | options: { 182 | variant: 'full', 183 | attribution: 'Tiles courtesy of OpenStreetMap Sweden — Map data {attribution.OpenStreetMap}' 184 | }, 185 | variants: { 186 | Full: 'full', 187 | Base: 'base', 188 | RoadsAndLabels: 'roads_and_labels' 189 | } 190 | }, 191 | MapQuestOpen: { 192 | /* Mapquest does support https, but with a different subdomain: 193 | * https://otile{s}-s.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext} 194 | * which makes implementing protocol relativity impossible. 195 | */ 196 | url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}', 197 | options: { 198 | type: 'map', 199 | ext: 'jpg', 200 | attribution: 201 | 'Tiles Courtesy of MapQuest — ' + 202 | 'Map data {attribution.OpenStreetMap}', 203 | subdomains: '1234' 204 | }, 205 | variants: { 206 | OSM: {}, 207 | Aerial: { 208 | options: { 209 | type: 'sat', 210 | attribution: 211 | 'Tiles Courtesy of MapQuest — ' + 212 | 'Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency' 213 | } 214 | }, 215 | HybridOverlay: { 216 | options: { 217 | type: 'hyb', 218 | ext: 'png', 219 | opacity: 0.9 220 | } 221 | } 222 | } 223 | }, 224 | MapBox: { 225 | url: '//api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', 226 | options: { 227 | attribution: 228 | 'Imagery from MapBox — ' + 229 | 'Map data {attribution.OpenStreetMap}', 230 | subdomains: 'abcd' 231 | } 232 | }, 233 | Stamen: { 234 | url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.png', 235 | options: { 236 | attribution: 237 | 'Map tiles by Stamen Design, ' + 238 | 'CC BY 3.0 — ' + 239 | 'Map data {attribution.OpenStreetMap}', 240 | subdomains: 'abcd', 241 | minZoom: 0, 242 | maxZoom: 20, 243 | variant: 'toner', 244 | ext: 'png' 245 | }, 246 | variants: { 247 | Toner: 'toner', 248 | TonerBackground: 'toner-background', 249 | TonerHybrid: 'toner-hybrid', 250 | TonerLines: 'toner-lines', 251 | TonerLabels: 'toner-labels', 252 | TonerLite: 'toner-lite', 253 | Watercolor: { 254 | options: { 255 | variant: 'watercolor', 256 | minZoom: 1, 257 | maxZoom: 16 258 | } 259 | }, 260 | Terrain: { 261 | options: { 262 | variant: 'terrain', 263 | minZoom: 4, 264 | maxZoom: 18, 265 | bounds: [[22, -132], [70, -56]] 266 | } 267 | }, 268 | TerrainBackground: { 269 | options: { 270 | variant: 'terrain-background', 271 | minZoom: 4, 272 | maxZoom: 18, 273 | bounds: [[22, -132], [70, -56]] 274 | } 275 | }, 276 | TopOSMRelief: { 277 | options: { 278 | variant: 'toposm-color-relief', 279 | ext: 'jpg', 280 | bounds: [[22, -132], [51, -56]] 281 | } 282 | }, 283 | TopOSMFeatures: { 284 | options: { 285 | variant: 'toposm-features', 286 | bounds: [[22, -132], [51, -56]], 287 | opacity: 0.9 288 | } 289 | } 290 | } 291 | }, 292 | Esri: { 293 | url: '//server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}', 294 | options: { 295 | variant: 'World_Street_Map', 296 | attribution: 'Tiles © Esri' 297 | }, 298 | variants: { 299 | WorldStreetMap: { 300 | options: { 301 | attribution: 302 | '{attribution.Esri} — ' + 303 | 'Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012' 304 | } 305 | }, 306 | DeLorme: { 307 | options: { 308 | variant: 'Specialty/DeLorme_World_Base_Map', 309 | minZoom: 1, 310 | maxZoom: 11, 311 | attribution: '{attribution.Esri} — Copyright: ©2012 DeLorme' 312 | } 313 | }, 314 | WorldTopoMap: { 315 | options: { 316 | variant: 'World_Topo_Map', 317 | attribution: 318 | '{attribution.Esri} — ' + 319 | 'Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community' 320 | } 321 | }, 322 | WorldImagery: { 323 | options: { 324 | variant: 'World_Imagery', 325 | attribution: 326 | '{attribution.Esri} — ' + 327 | 'Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' 328 | } 329 | }, 330 | WorldTerrain: { 331 | options: { 332 | variant: 'World_Terrain_Base', 333 | maxZoom: 13, 334 | attribution: 335 | '{attribution.Esri} — ' + 336 | 'Source: USGS, Esri, TANA, DeLorme, and NPS' 337 | } 338 | }, 339 | WorldShadedRelief: { 340 | options: { 341 | variant: 'World_Shaded_Relief', 342 | maxZoom: 13, 343 | attribution: '{attribution.Esri} — Source: Esri' 344 | } 345 | }, 346 | WorldPhysical: { 347 | options: { 348 | variant: 'World_Physical_Map', 349 | maxZoom: 8, 350 | attribution: '{attribution.Esri} — Source: US National Park Service' 351 | } 352 | }, 353 | OceanBasemap: { 354 | options: { 355 | variant: 'Ocean_Basemap', 356 | maxZoom: 13, 357 | attribution: '{attribution.Esri} — Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri' 358 | } 359 | }, 360 | NatGeoWorldMap: { 361 | options: { 362 | variant: 'NatGeo_World_Map', 363 | maxZoom: 16, 364 | attribution: '{attribution.Esri} — National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC' 365 | } 366 | }, 367 | WorldGrayCanvas: { 368 | options: { 369 | variant: 'Canvas/World_Light_Gray_Base', 370 | maxZoom: 16, 371 | attribution: '{attribution.Esri} — Esri, DeLorme, NAVTEQ' 372 | } 373 | } 374 | } 375 | }, 376 | OpenWeatherMap: { 377 | url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png', 378 | options: { 379 | maxZoom: 19, 380 | attribution: 'Map data © OpenWeatherMap', 381 | opacity: 0.5 382 | }, 383 | variants: { 384 | Clouds: 'clouds', 385 | CloudsClassic: 'clouds_cls', 386 | Precipitation: 'precipitation', 387 | PrecipitationClassic: 'precipitation_cls', 388 | Rain: 'rain', 389 | RainClassic: 'rain_cls', 390 | Pressure: 'pressure', 391 | PressureContour: 'pressure_cntr', 392 | Wind: 'wind', 393 | Temperature: 'temp', 394 | Snow: 'snow' 395 | } 396 | }, 397 | HERE: { 398 | /* 399 | * HERE maps, formerly Nokia maps. 400 | * These basemaps are free, but you need an API key. Please sign up at 401 | * http://developer.here.com/getting-started 402 | * 403 | * Note that the base urls contain '.cit' whichs is HERE's 404 | * 'Customer Integration Testing' environment. Please remove for production 405 | * envirionments. 406 | */ 407 | url: 408 | '//{s}.{base}.maps.cit.api.here.com/maptile/2.1/' + 409 | 'maptile/{mapID}/{variant}/{z}/{x}/{y}/256/png8?' + 410 | 'app_id={app_id}&app_code={app_code}', 411 | options: { 412 | attribution: 413 | 'Map © 1987-2014 HERE', 414 | subdomains: '1234', 415 | mapID: 'newest', 416 | 'app_id': '', 417 | 'app_code': '', 418 | base: 'base', 419 | variant: 'normal.day', 420 | maxZoom: 20 421 | }, 422 | variants: { 423 | normalDay: 'normal.day', 424 | normalDayCustom: 'normal.day.custom', 425 | normalDayGrey: 'normal.day.grey', 426 | normalDayMobile: 'normal.day.mobile', 427 | normalDayGreyMobile: 'normal.day.grey.mobile', 428 | normalDayTransit: 'normal.day.transit', 429 | normalDayTransitMobile: 'normal.day.transit.mobile', 430 | normalNight: 'normal.night', 431 | normalNightMobile: 'normal.night.mobile', 432 | normalNightGrey: 'normal.night.grey', 433 | normalNightGreyMobile: 'normal.night.grey.mobile', 434 | 435 | carnavDayGrey: 'carnav.day.grey', 436 | hybridDay: { 437 | options: { 438 | base: 'aerial', 439 | variant: 'hybrid.day' 440 | } 441 | }, 442 | hybridDayMobile: { 443 | options: { 444 | base: 'aerial', 445 | variant: 'hybrid.day.mobile' 446 | } 447 | }, 448 | pedestrianDay: 'pedestrian.day', 449 | pedestrianNight: 'pedestrian.night', 450 | satelliteDay: { 451 | options: { 452 | base: 'aerial', 453 | variant: 'satellite.day' 454 | } 455 | }, 456 | terrainDay: { 457 | options: { 458 | base: 'aerial', 459 | variant: 'terrain.day' 460 | } 461 | }, 462 | terrainDayMobile: { 463 | options: { 464 | base: 'aerial', 465 | variant: 'terrain.day.mobile' 466 | } 467 | } 468 | } 469 | }, 470 | Acetate: { 471 | url: 'http://a{s}.acetate.geoiq.com/tiles/{variant}/{z}/{x}/{y}.png', 472 | options: { 473 | attribution: 474 | '©2012 Esri & Stamen, Data from OSM and Natural Earth', 475 | subdomains: '0123', 476 | minZoom: 2, 477 | maxZoom: 18, 478 | variant: 'acetate-base' 479 | }, 480 | variants: { 481 | basemap: 'acetate-base', 482 | terrain: 'terrain', 483 | all: 'acetate-hillshading', 484 | foreground: 'acetate-fg', 485 | roads: 'acetate-roads', 486 | labels: 'acetate-labels', 487 | hillshading: 'hillshading' 488 | } 489 | }, 490 | FreeMapSK: { 491 | url: 'http://{s}.freemap.sk/T/{z}/{x}/{y}.jpeg', 492 | options: { 493 | minZoom: 8, 494 | maxZoom: 16, 495 | subdomains: ['t1', 't2', 't3', 't4'], 496 | attribution: 497 | '{attribution.OpenStreetMap}, vizualization CC-By-SA 2.0 Freemap.sk' 498 | } 499 | }, 500 | MtbMap: { 501 | url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', 502 | options: { 503 | attribution: 504 | '{attribution.OpenStreetMap} & USGS' 505 | } 506 | }, 507 | CartoDB: { 508 | url: 'http://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}.png', 509 | options: { 510 | attribution: '{attribution.OpenStreetMap} © CartoDB', 511 | subdomains: 'abcd', 512 | maxZoom: 19, 513 | variant: 'light_all' 514 | }, 515 | variants: { 516 | Positron: 'light_all', 517 | PositronNoLabels: 'light_nolabels', 518 | PositronOnlyLabels: 'light_only_labels', 519 | DarkMatter: 'dark_all', 520 | DarkMatterNoLabels: 'dark_nolabels', 521 | DarkMatterOnlyLabels: 'dark_only_labels' 522 | } 523 | }, 524 | HikeBike: { 525 | url: 'http://{s}.tiles.wmflabs.org/{variant}/{z}/{x}/{y}.png', 526 | options: { 527 | maxZoom: 19, 528 | attribution: '{attribution.OpenStreetMap}', 529 | variant: 'hikebike' 530 | }, 531 | variants: { 532 | HikeBike: {}, 533 | HillShading: { 534 | options: { 535 | maxZoom: 15, 536 | variant: 'hillshading' 537 | } 538 | } 539 | } 540 | }, 541 | BasemapAT: { 542 | url: '//maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}', 543 | options: { 544 | maxZoom: 19, 545 | attribution: 'Datenquelle: basemap.at', 546 | subdomains: ['', '1', '2', '3', '4'], 547 | format: 'png', 548 | bounds: [[46.358770, 8.782379], [49.037872, 17.189532]], 549 | variant: 'geolandbasemap' 550 | }, 551 | variants: { 552 | basemap: 'geolandbasemap', 553 | grau: 'bmapgrau', 554 | overlay: 'bmapoverlay', 555 | highdpi: { 556 | options: { 557 | variant: 'bmaphidpi', 558 | format: 'jpeg' 559 | } 560 | }, 561 | orthofoto: { 562 | options: { 563 | variant: 'bmaporthofoto30cm', 564 | format: 'jpeg' 565 | } 566 | } 567 | } 568 | }, 569 | NASAGIBS: { 570 | url: '//map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}', 571 | options: { 572 | attribution: 573 | 'Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System ' + 574 | '(ESDIS) with funding provided by NASA/HQ.', 575 | bounds: [[-85.0511287776, -179.999999975], [85.0511287776, 179.999999975]], 576 | minZoom: 1, 577 | maxZoom: 9, 578 | format: 'jpg', 579 | time: '', 580 | tilematrixset: 'GoogleMapsCompatible_Level' 581 | }, 582 | variants: { 583 | ModisTerraTrueColorCR: 'MODIS_Terra_CorrectedReflectance_TrueColor', 584 | ModisTerraBands367CR: 'MODIS_Terra_CorrectedReflectance_Bands367', 585 | ViirsEarthAtNight2012: { 586 | options: { 587 | variant: 'VIIRS_CityLights_2012', 588 | maxZoom: 8 589 | } 590 | }, 591 | ModisTerraLSTDay: { 592 | options: { 593 | variant: 'MODIS_Terra_Land_Surface_Temp_Day', 594 | format: 'png', 595 | maxZoom: 7, 596 | opacity: 0.75 597 | } 598 | }, 599 | ModisTerraSnowCover: { 600 | options: { 601 | variant: 'MODIS_Terra_Snow_Cover', 602 | format: 'png', 603 | maxZoom: 8, 604 | opacity: 0.75 605 | } 606 | }, 607 | ModisTerraAOD: { 608 | options: { 609 | variant: 'MODIS_Terra_Aerosol', 610 | format: 'png', 611 | maxZoom: 6, 612 | opacity: 0.75 613 | } 614 | }, 615 | ModisTerraChlorophyll: { 616 | options: { 617 | variant: 'MODIS_Terra_Chlorophyll_A', 618 | format: 'png', 619 | maxZoom: 7, 620 | opacity: 0.75 621 | } 622 | } 623 | } 624 | } 625 | }; 626 | 627 | L.tileLayer.provider = function (provider, options) { 628 | return new L.TileLayer.Provider(provider, options); 629 | }; 630 | 631 | return L; 632 | })); 633 | -------------------------------------------------------------------------------- /js/leaflet.label.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Smartrak, Jacob Toye 3 | Leaflet.label is an open-source JavaScript library for adding labels to markers and paths on leaflet powered maps. 4 | https://github.com/jacobtoye/Leaflet.label 5 | */ 6 | (function(e,t){L.Label=L.Popup.extend({options:{autoPan:!1,className:"",closePopupOnClick:!1,noHide:!1,offset:new L.Point(12,-15)},onAdd:function(e){this._map=e,this._container||this._initLayout(),this._updateContent();var t=e.options.fadeAnimation;t&&L.DomUtil.setOpacity(this._container,0),e._panes.popupPane.appendChild(this._container),e.on("viewreset",this._updatePosition,this),L.Browser.any3d&&e.on("zoomanim",this._zoomAnimation,this),this._update(),t&&L.DomUtil.setOpacity(this._container,1)},close:function(){var e=this._map;e&&(e._label=null,e.removeLayer(this))},_initLayout:function(){this._container=L.DomUtil.create("div","leaflet-label "+this.options.className+" leaflet-zoom-animated")},_updateContent:function(){if(!this._content)return;typeof this._content=="string"&&(this._container.innerHTML=this._content)},_updateLayout:function(){},_updatePosition:function(){var e=this._map.latLngToLayerPoint(this._latlng);this._setPosition(e)},_setPosition:function(e){e=e.add(this.options.offset),L.DomUtil.setPosition(this._container,e)},_zoomAnimation:function(e){var t=this._map._latLngToNewLayerPoint(this._latlng,e.zoom,e.center);this._setPosition(t)}}),L.Icon.Default.mergeOptions({labelAnchor:new L.Point(9,-20)}),L.Marker.mergeOptions({icon:new L.Icon.Default}),L.Marker.include({showLabel:function(){return this._label&&this._map&&(this._label.setLatLng(this._latlng),this._map.showLabel(this._label)),this},hideLabel:function(){return this._label&&this._label.close(),this},bindLabel:function(e,t){var n=L.point(this.options.icon.options.labelAnchor)||new L.Point(0,0);return n=n.add(L.Label.prototype.options.offset),t&&t.offset&&(n=n.add(t.offset)),t=L.Util.extend({offset:n},t),!this._label&&!t.noHide&&(this.on("mouseover",this.showLabel,this).on("mouseout",this.hideLabel,this),L.Browser.touch&&this.on("click",this.showLabel,this),this._haslabelHandlers=!0),this._label=(new L.Label(t,this)).setContent(e),this},unbindLabel:function(){return this._label&&(this._label=null,this._haslabelHandlers&&(this.off("mouseover",this.showLabel).off("mouseout",this.hideLabel),L.Browser.touch&&this.of("click",this.showLabel)),this._haslabelHandlers=!1),this},updateLabelContent:function(e){this._label&&this._label.setContent(e)}}),L.Path.include({bindLabel:function(e,t){if(!this._label||this._label.options!==t)this._label=new L.Label(t,this);return this._label.setContent(e),this._showLabelAdded||(this.on("mouseover",this._showLabel,this).on("mousemove",this._moveLabel,this).on("mouseout",this._hideLabel,this),L.Browser.touch&&this.on("click",this._showLabel,this),this._showLabelAdded=!0),this},unbindLabel:function(){return this._label&&(this._label=null,this._showLabelAdded=!1,this.off("mouseover",this._showLabel).off("mousemove",this._moveLabel).off("mouseout",this._hideLabel)),this},updateLabelContent:function(e){this._label&&this._label.setContent(e)},_showLabel:function(e){this._label.setLatLng(e.latlng),this._map.showLabel(this._label)},_moveLabel:function(e){this._label.setLatLng(e.latlng)},_hideLabel:function(){this._label.close()}}),L.Map.include({showLabel:function(e){return this._label=e,this.addLayer(e)}})})(this); -------------------------------------------------------------------------------- /js/maps.js: -------------------------------------------------------------------------------- 1 | var map; 2 | var markersLayer; 3 | var heatmapDataset = []; 4 | 5 | function addLabel(feature, layer) { 6 | if (feature.properties && feature.properties.name) { 7 | layer.bindLabel(''+feature.properties.name+''); 8 | } 9 | } 10 | 11 | function show(feature, layer){ 12 | return feature.properties.show_on_map; 13 | } 14 | 15 | function addFeature(feature){ 16 | markersLayer.addLayer(L.geoJson(feature, { 17 | onEachFeature: addLabel, 18 | filter: show 19 | })); 20 | 21 | heatmapDataset.push({ 22 | lon: feature.geometry.coordinates[0], 23 | lat: feature.geometry.coordinates[1] 24 | }); 25 | } 26 | 27 | function finishInit(){ 28 | heatmapLayer.setData(heatmapDataset); 29 | markersLayer.addTo(map); 30 | 31 | overlays = { 32 | "Markers": markersLayer, 33 | "Heatmap": heatmapLayer 34 | }; 35 | 36 | L.control.layers(overlays, null, {collapsed: false}) 37 | .setPosition("bottomleft") 38 | .addTo(map); 39 | } 40 | 41 | window.onload = function() { 42 | map = L.map('map').setView([23.26, 0], 3); 43 | 44 | // list of tile providers can be seen here: http://leaflet-extras.github.io/leaflet-providers/preview/ 45 | baseLayer = L.tileLayer.provider('Thunderforest.Landscape', { 46 | // write options and credentials here 47 | // id: 'MAPBOX_ID', 48 | // accessToken: 'MAPBOX_TOKEN' 49 | }).addTo(map); 50 | 51 | markersLayer = L.geoJson(); 52 | 53 | heatmapLayer = L.TileLayer.heatMap({ 54 | radius: { value: 40, absolute: false }, 55 | opacity: 1, 56 | gradient: { 57 | 0.45: "rgb(0,0,255)", 58 | 0.55: "rgb(0,255,255)", 59 | 0.65: "rgb(0,255,0)", 60 | 0.95: "rgb(255,0,0)" 61 | } 62 | }); 63 | 64 | $.getJSON('places.geojson', function(data) { 65 | $.each(data.features, function(i, feat) { 66 | addFeature(feat); 67 | }); 68 | finishInit(); 69 | }); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /places.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | { 4 | "geometry": { 5 | "coordinates": [ 6 | 116.3883, 7 | 39.9289 8 | ], 9 | "type": "Point" 10 | }, 11 | "properties": { 12 | "name": "211.103.155.229", 13 | "place": "Beijing, China", 14 | "show_on_map": true 15 | }, 16 | "type": "Feature" 17 | }, 18 | { 19 | "geometry": { 20 | "coordinates": [ 21 | 116.41869280671627, 22 | 39.87539056337965 23 | ], 24 | "type": "Point" 25 | }, 26 | "properties": { 27 | "name": "61.236.64.56", 28 | "place": "Beijing, China", 29 | "show_on_map": true 30 | }, 31 | "type": "Feature" 32 | }, 33 | { 34 | "geometry": { 35 | "coordinates": [ 36 | 104.0667, 37 | 30.6667 38 | ], 39 | "type": "Point" 40 | }, 41 | "properties": { 42 | "name": "182.131.22.211", 43 | "place": "Chengdu, China", 44 | "show_on_map": true 45 | }, 46 | "type": "Feature" 47 | }, 48 | { 49 | "geometry": { 50 | "coordinates": [ 51 | 113.5325, 52 | 34.6836 53 | ], 54 | "type": "Point" 55 | }, 56 | "properties": { 57 | "name": "218.29.228.34", 58 | "place": "Zhengzhou, China", 59 | "show_on_map": true 60 | }, 61 | "type": "Feature" 62 | }, 63 | { 64 | "geometry": { 65 | "coordinates": [ 66 | -118.2512, 67 | 34.0438 68 | ], 69 | "type": "Point" 70 | }, 71 | "properties": { 72 | "name": "173.234.214.22", 73 | "place": "Los Angeles, United States", 74 | "show_on_map": true 75 | }, 76 | "type": "Feature" 77 | }, 78 | { 79 | "geometry": { 80 | "coordinates": [ 81 | 120.1614, 82 | 30.2936 83 | ], 84 | "type": "Point" 85 | }, 86 | "properties": { 87 | "name": "115.195.94.205", 88 | "place": "Hangzhou, China", 89 | "show_on_map": true 90 | }, 91 | "type": "Feature" 92 | }, 93 | { 94 | "geometry": { 95 | "coordinates": [ 96 | 120.24598596740574, 97 | 30.221383333049864 98 | ], 99 | "type": "Point" 100 | }, 101 | "properties": { 102 | "name": "60.176.104.59", 103 | "place": "Hangzhou, China", 104 | "show_on_map": true 105 | }, 106 | "type": "Feature" 107 | }, 108 | { 109 | "geometry": { 110 | "coordinates": [ 111 | 120.26020928155704, 112 | 30.208290082075575 113 | ], 114 | "type": "Point" 115 | }, 116 | "properties": { 117 | "name": "115.197.106.60", 118 | "place": "Hangzhou, China", 119 | "show_on_map": true 120 | }, 121 | "type": "Feature" 122 | }, 123 | { 124 | "geometry": { 125 | "coordinates": [ 126 | 118.7778, 127 | 32.0617 128 | ], 129 | "type": "Point" 130 | }, 131 | "properties": { 132 | "name": "61.160.247.105", 133 | "place": "Nanjing, China", 134 | "show_on_map": true 135 | }, 136 | "type": "Feature" 137 | }, 138 | { 139 | "geometry": { 140 | "coordinates": [ 141 | 120.12554614698125, 142 | 30.23017226619576 143 | ], 144 | "type": "Point" 145 | }, 146 | "properties": { 147 | "name": "60.176.109.19", 148 | "place": "Hangzhou, China", 149 | "show_on_map": true 150 | }, 151 | "type": "Feature" 152 | }, 153 | { 154 | "geometry": { 155 | "coordinates": [ 156 | 120.18552669066204, 157 | 30.324993264942986 158 | ], 159 | "type": "Point" 160 | }, 161 | "properties": { 162 | "name": "115.197.108.125", 163 | "place": "Hangzhou, China", 164 | "show_on_map": true 165 | }, 166 | "type": "Feature" 167 | }, 168 | { 169 | "geometry": { 170 | "coordinates": [ 171 | 120.07071393782981, 172 | 30.34417906911845 173 | ], 174 | "type": "Point" 175 | }, 176 | "properties": { 177 | "name": "115.195.90.181", 178 | "place": "Hangzhou, China", 179 | "show_on_map": true 180 | }, 181 | "type": "Feature" 182 | }, 183 | { 184 | "geometry": { 185 | "coordinates": [ 186 | 120.17031274344153, 187 | 30.37075814666442 188 | ], 189 | "type": "Point" 190 | }, 191 | "properties": { 192 | "name": "115.196.95.172", 193 | "place": "Hangzhou, China", 194 | "show_on_map": true 195 | }, 196 | "type": "Feature" 197 | }, 198 | { 199 | "geometry": { 200 | "coordinates": [ 201 | 120.08471801675354, 202 | 30.292992511029354 203 | ], 204 | "type": "Point" 205 | }, 206 | "properties": { 207 | "name": "115.197.107.74", 208 | "place": "Hangzhou, China", 209 | "show_on_map": true 210 | }, 211 | "type": "Feature" 212 | }, 213 | { 214 | "geometry": { 215 | "coordinates": [ 216 | 120.15346587403789, 217 | 30.203399321383387 218 | ], 219 | "type": "Point" 220 | }, 221 | "properties": { 222 | "name": "115.200.250.162", 223 | "place": "Hangzhou, China", 224 | "show_on_map": true 225 | }, 226 | "type": "Feature" 227 | }, 228 | { 229 | "geometry": { 230 | "coordinates": [ 231 | 120.1680303506146, 232 | 30.260829035358007 233 | ], 234 | "type": "Point" 235 | }, 236 | "properties": { 237 | "name": "125.118.222.138", 238 | "place": "Hangzhou, China", 239 | "show_on_map": true 240 | }, 241 | "type": "Feature" 242 | }, 243 | { 244 | "geometry": { 245 | "coordinates": [ 246 | 120.25910474949279, 247 | 30.228807592211478 248 | ], 249 | "type": "Point" 250 | }, 251 | "properties": { 252 | "name": "60.176.107.79", 253 | "place": "Hangzhou, China", 254 | "show_on_map": true 255 | }, 256 | "type": "Feature" 257 | }, 258 | { 259 | "geometry": { 260 | "coordinates": [ 261 | 120.21547089641327, 262 | 30.274896846551304 263 | ], 264 | "type": "Point" 265 | }, 266 | "properties": { 267 | "name": "125.118.223.215", 268 | "place": "Hangzhou, China", 269 | "show_on_map": true 270 | }, 271 | "type": "Feature" 272 | }, 273 | { 274 | "geometry": { 275 | "coordinates": [ 276 | 120.16637649335196, 277 | 30.207832201195068 278 | ], 279 | "type": "Point" 280 | }, 281 | "properties": { 282 | "name": "115.200.249.150", 283 | "place": "Hangzhou, China", 284 | "show_on_map": true 285 | }, 286 | "type": "Feature" 287 | }, 288 | { 289 | "geometry": { 290 | "coordinates": [ 291 | 120.20324969693431, 292 | 30.33891121558553 293 | ], 294 | "type": "Point" 295 | }, 296 | "properties": { 297 | "name": "115.195.88.130", 298 | "place": "Hangzhou, China", 299 | "show_on_map": true 300 | }, 301 | "type": "Feature" 302 | }, 303 | { 304 | "geometry": { 305 | "coordinates": [ 306 | 125.3228, 307 | 43.88 308 | ], 309 | "type": "Point" 310 | }, 311 | "properties": { 312 | "name": "218.27.252.2", 313 | "place": "Changchun, China", 314 | "show_on_map": true 315 | }, 316 | "type": "Feature" 317 | }, 318 | { 319 | "geometry": { 320 | "coordinates": [ 321 | 110.5829, 322 | 21.3226 323 | ], 324 | "type": "Point" 325 | }, 326 | "properties": { 327 | "name": "61.142.106.34", 328 | "place": "Zhongshan, China", 329 | "show_on_map": true 330 | }, 331 | "type": "Feature" 332 | }, 333 | { 334 | "geometry": { 335 | "coordinates": [ 336 | 113.62272594310282, 337 | 34.64706630182102 338 | ], 339 | "type": "Point" 340 | }, 341 | "properties": { 342 | "name": "61.158.164.59", 343 | "place": "Zhengzhou, China", 344 | "show_on_map": true 345 | }, 346 | "type": "Feature" 347 | }, 348 | { 349 | "geometry": { 350 | "coordinates": [ 351 | 124.6431, 352 | 8.4811 353 | ], 354 | "type": "Point" 355 | }, 356 | "properties": { 357 | "name": "202.85.209.50", 358 | "place": "Cagayan De Oro, Philippines", 359 | "show_on_map": true 360 | }, 361 | "type": "Feature" 362 | }, 363 | { 364 | "geometry": { 365 | "coordinates": [ 366 | 114.2734, 367 | 30.5801 368 | ], 369 | "type": "Point" 370 | }, 371 | "properties": { 372 | "name": "119.36.186.44", 373 | "place": "Wuhan, China", 374 | "show_on_map": true 375 | }, 376 | "type": "Feature" 377 | }, 378 | { 379 | "geometry": { 380 | "coordinates": [ 381 | 116.4833986678477, 382 | 39.9806336209436 383 | ], 384 | "type": "Point" 385 | }, 386 | "properties": { 387 | "name": "111.1.45.163", 388 | "place": "Beijing, China", 389 | "show_on_map": true 390 | }, 391 | "type": "Feature" 392 | }, 393 | { 394 | "geometry": { 395 | "coordinates": [ 396 | 31.2833, 397 | 58.5167 398 | ], 399 | "type": "Point" 400 | }, 401 | "properties": { 402 | "name": "82.140.132.161", 403 | "place": "Velikiy Novgorod, Russian Federation", 404 | "show_on_map": true 405 | }, 406 | "type": "Feature" 407 | }, 408 | { 409 | "geometry": { 410 | "coordinates": [ 411 | 127.02, 412 | 37.4906 413 | ], 414 | "type": "Point" 415 | }, 416 | "properties": { 417 | "name": "218.236.241.180", 418 | "place": "Seocho, Korea, Republic of", 419 | "show_on_map": true 420 | }, 421 | "type": "Feature" 422 | }, 423 | { 424 | "geometry": { 425 | "coordinates": [ 426 | 116.45408036404179, 427 | 39.87680566177656 428 | ], 429 | "type": "Point" 430 | }, 431 | "properties": { 432 | "name": "42.96.131.16", 433 | "place": "Beijing, China", 434 | "show_on_map": true 435 | }, 436 | "type": "Feature" 437 | }, 438 | { 439 | "geometry": { 440 | "coordinates": [ 441 | 103.97688401638489, 442 | 30.635238583372235 443 | ], 444 | "type": "Point" 445 | }, 446 | "properties": { 447 | "name": "125.64.43.154", 448 | "place": "Chengdu, China", 449 | "show_on_map": true 450 | }, 451 | "type": "Feature" 452 | }, 453 | { 454 | "geometry": { 455 | "coordinates": [ 456 | 114.28680209341704, 457 | 30.573981662913244 458 | ], 459 | "type": "Point" 460 | }, 461 | "properties": { 462 | "name": "61.183.9.151", 463 | "place": "Wuhan, China", 464 | "show_on_map": true 465 | }, 466 | "type": "Feature" 467 | }, 468 | { 469 | "geometry": { 470 | "coordinates": [ 471 | 114.19895546458505, 472 | 30.58498155076424 473 | ], 474 | "type": "Point" 475 | }, 476 | "properties": { 477 | "name": "221.234.42.9", 478 | "place": "Wuhan, China", 479 | "show_on_map": true 480 | }, 481 | "type": "Feature" 482 | }, 483 | { 484 | "geometry": { 485 | "coordinates": [ 486 | 18.9664, 487 | 50.1372 488 | ], 489 | "type": "Point" 490 | }, 491 | "properties": { 492 | "name": "109.95.211.27", 493 | "place": "Tychy, Poland", 494 | "show_on_map": true 495 | }, 496 | "type": "Feature" 497 | }, 498 | { 499 | "geometry": { 500 | "coordinates": [ 501 | 12.4667, 502 | 42.5 503 | ], 504 | "type": "Point" 505 | }, 506 | "properties": { 507 | "name": "176.58.117.132", 508 | "place": "Montoro, Italy", 509 | "show_on_map": true 510 | }, 511 | "type": "Feature" 512 | }, 513 | { 514 | "geometry": { 515 | "coordinates": [ 516 | 126.9783, 517 | 37.5985 518 | ], 519 | "type": "Point" 520 | }, 521 | "properties": { 522 | "name": "59.29.242.179", 523 | "place": "Seoul, Korea, Republic of", 524 | "show_on_map": true 525 | }, 526 | "type": "Feature" 527 | }, 528 | { 529 | "geometry": { 530 | "coordinates": [ 531 | 116.38628633975874, 532 | 40.01038710875119 533 | ], 534 | "type": "Point" 535 | }, 536 | "properties": { 537 | "name": "222.128.198.6", 538 | "place": "Beijing, China", 539 | "show_on_map": true 540 | }, 541 | "type": "Feature" 542 | }, 543 | { 544 | "geometry": { 545 | "coordinates": [ 546 | 113.25, 547 | 23.1167 548 | ], 549 | "type": "Point" 550 | }, 551 | "properties": { 552 | "name": "121.8.154.28", 553 | "place": "Guangzhou, China", 554 | "show_on_map": true 555 | }, 556 | "type": "Feature" 557 | }, 558 | { 559 | "geometry": { 560 | "coordinates": [ 561 | 116.4772514837756, 562 | 39.8462325042993 563 | ], 564 | "type": "Point" 565 | }, 566 | "properties": { 567 | "name": "42.96.143.110", 568 | "place": "Beijing, China", 569 | "show_on_map": true 570 | }, 571 | "type": "Feature" 572 | }, 573 | { 574 | "geometry": { 575 | "coordinates": [ 576 | 116.3945445200536, 577 | 39.86643364318856 578 | ], 579 | "type": "Point" 580 | }, 581 | "properties": { 582 | "name": "123.196.113.11", 583 | "place": "Beijing, China", 584 | "show_on_map": true 585 | }, 586 | "type": "Feature" 587 | }, 588 | { 589 | "geometry": { 590 | "coordinates": [ 591 | 106.9167, 592 | 47.9167 593 | ], 594 | "type": "Point" 595 | }, 596 | "properties": { 597 | "name": "202.21.111.140", 598 | "place": "Ulaanbaatar, Mongolia", 599 | "show_on_map": true 600 | }, 601 | "type": "Feature" 602 | }, 603 | { 604 | "geometry": { 605 | "coordinates": [ 606 | 121.0509, 607 | 14.6488 608 | ], 609 | "type": "Point" 610 | }, 611 | "properties": { 612 | "name": "124.105.60.29", 613 | "place": "Quezon City, Philippines", 614 | "show_on_map": true 615 | }, 616 | "type": "Feature" 617 | }, 618 | { 619 | "geometry": { 620 | "coordinates": [ 621 | 113.29139329339615, 622 | 23.120420904103707 623 | ], 624 | "type": "Point" 625 | }, 626 | "properties": { 627 | "name": "121.8.148.61", 628 | "place": "Guangzhou, China", 629 | "show_on_map": true 630 | }, 631 | "type": "Feature" 632 | }, 633 | { 634 | "geometry": { 635 | "coordinates": [ 636 | 15.5058, 637 | 51.9393 638 | ], 639 | "type": "Point" 640 | }, 641 | "properties": { 642 | "name": "91.230.60.187", 643 | "place": "Zielona G\u00f3ra, Poland", 644 | "show_on_map": true 645 | }, 646 | "type": "Feature" 647 | }, 648 | { 649 | "geometry": { 650 | "coordinates": [ 651 | 121.3997, 652 | 31.0456 653 | ], 654 | "type": "Point" 655 | }, 656 | "properties": { 657 | "name": "58.246.251.230", 658 | "place": "Shanghai, China", 659 | "show_on_map": true 660 | }, 661 | "type": "Feature" 662 | }, 663 | { 664 | "geometry": { 665 | "coordinates": [ 666 | 104.15315014440925, 667 | 30.59297972560421 668 | ], 669 | "type": "Point" 670 | }, 671 | "properties": { 672 | "name": "221.10.112.134", 673 | "place": "Chengdu, China", 674 | "show_on_map": true 675 | }, 676 | "type": "Feature" 677 | }, 678 | { 679 | "geometry": { 680 | "coordinates": [ 681 | 9.2, 682 | 45.4667 683 | ], 684 | "type": "Point" 685 | }, 686 | "properties": { 687 | "name": "109.168.103.76", 688 | "place": "Milan, Italy", 689 | "show_on_map": true 690 | }, 691 | "type": "Feature" 692 | }, 693 | { 694 | "geometry": { 695 | "coordinates": [ 696 | 118.0819, 697 | 24.4798 698 | ], 699 | "type": "Point" 700 | }, 701 | "properties": { 702 | "name": "222.76.211.149", 703 | "place": "Xiamen, China", 704 | "show_on_map": true 705 | }, 706 | "type": "Feature" 707 | }, 708 | { 709 | "geometry": { 710 | "coordinates": [ 711 | 126.93429019939693, 712 | 37.666553682287685 713 | ], 714 | "type": "Point" 715 | }, 716 | "properties": { 717 | "name": "115.95.166.247", 718 | "place": "Seoul, Korea, Republic of", 719 | "show_on_map": true 720 | }, 721 | "type": "Feature" 722 | }, 723 | { 724 | "geometry": { 725 | "coordinates": [ 726 | -46.6658, 727 | -23.4733 728 | ], 729 | "type": "Point" 730 | }, 731 | "properties": { 732 | "name": "200.207.223.73", 733 | "place": "S\u00e3o Paulo, Brazil", 734 | "show_on_map": true 735 | }, 736 | "type": "Feature" 737 | }, 738 | { 739 | "geometry": { 740 | "coordinates": [ 741 | 113.20813142349924, 742 | 23.205593866574745 743 | ], 744 | "type": "Point" 745 | }, 746 | "properties": { 747 | "name": "114.113.199.154", 748 | "place": "Guangzhou, China", 749 | "show_on_map": true 750 | }, 751 | "type": "Feature" 752 | }, 753 | { 754 | "geometry": { 755 | "coordinates": [ 756 | 120.06419108294612, 757 | 30.344271327331825 758 | ], 759 | "type": "Point" 760 | }, 761 | "properties": { 762 | "name": "122.226.160.19", 763 | "place": "Hangzhou, China", 764 | "show_on_map": true 765 | }, 766 | "type": "Feature" 767 | }, 768 | { 769 | "geometry": { 770 | "coordinates": [ 771 | 115.275, 772 | 39.8897 773 | ], 774 | "type": "Point" 775 | }, 776 | "properties": { 777 | "name": "60.10.203.18", 778 | "place": "Hebei, China", 779 | "show_on_map": true 780 | }, 781 | "type": "Feature" 782 | }, 783 | { 784 | "geometry": { 785 | "coordinates": [ 786 | 37.6156, 787 | 55.7522 788 | ], 789 | "type": "Point" 790 | }, 791 | "properties": { 792 | "name": "89.108.120.17", 793 | "place": "Moscow, Russian Federation", 794 | "show_on_map": true 795 | }, 796 | "type": "Feature" 797 | }, 798 | { 799 | "geometry": { 800 | "coordinates": [ 801 | 114.23594503464477, 802 | 30.597199885259077 803 | ], 804 | "type": "Point" 805 | }, 806 | "properties": { 807 | "name": "202.103.36.43", 808 | "place": "Wuhan, China", 809 | "show_on_map": true 810 | }, 811 | "type": "Feature" 812 | }, 813 | { 814 | "geometry": { 815 | "coordinates": [ 816 | 104.12697969664883, 817 | 30.62211155213422 818 | ], 819 | "type": "Point" 820 | }, 821 | "properties": { 822 | "name": "61.157.243.82", 823 | "place": "Chengdu, China", 824 | "show_on_map": true 825 | }, 826 | "type": "Feature" 827 | }, 828 | { 829 | "geometry": { 830 | "coordinates": [ 831 | 28.9647, 832 | 41.0186 833 | ], 834 | "type": "Point" 835 | }, 836 | "properties": { 837 | "name": "31.210.110.205", 838 | "place": "Istanbul, Turkey", 839 | "show_on_map": true 840 | }, 841 | "type": "Feature" 842 | }, 843 | { 844 | "geometry": { 845 | "coordinates": [ 846 | -3.0092, 847 | 43.3282 848 | ], 849 | "type": "Point" 850 | }, 851 | "properties": { 852 | "name": "212.55.8.177", 853 | "place": "Las Arenas, Spain", 854 | "show_on_map": true 855 | }, 856 | "type": "Feature" 857 | }, 858 | { 859 | "geometry": { 860 | "coordinates": [ 861 | -99.1386, 862 | 19.4342 863 | ], 864 | "type": "Point" 865 | }, 866 | "properties": { 867 | "name": "201.151.198.117", 868 | "place": "Mexico, Mexico", 869 | "show_on_map": true 870 | }, 871 | "type": "Feature" 872 | }, 873 | { 874 | "geometry": { 875 | "coordinates": [ 876 | 106.8294, 877 | -6.1744 878 | ], 879 | "type": "Point" 880 | }, 881 | "properties": { 882 | "name": "222.124.3.248", 883 | "place": "Jakarta, Indonesia", 884 | "show_on_map": true 885 | }, 886 | "type": "Feature" 887 | }, 888 | { 889 | "geometry": { 890 | "coordinates": [ 891 | 120.1027827133581, 892 | 30.382100971643805 893 | ], 894 | "type": "Point" 895 | }, 896 | "properties": { 897 | "name": "124.160.194.27", 898 | "place": "Hangzhou, China", 899 | "show_on_map": true 900 | }, 901 | "type": "Feature" 902 | }, 903 | { 904 | "geometry": { 905 | "coordinates": [ 906 | 116.3991829244609, 907 | 39.89601221559162 908 | ], 909 | "type": "Point" 910 | }, 911 | "properties": { 912 | "name": "118.244.14.49", 913 | "place": "Beijing, China", 914 | "show_on_map": true 915 | }, 916 | "type": "Feature" 917 | }, 918 | { 919 | "geometry": { 920 | "coordinates": [ 921 | 127.4897, 922 | 36.6372 923 | ], 924 | "type": "Point" 925 | }, 926 | "properties": { 927 | "name": "121.191.8.10", 928 | "place": "Cheongju, Korea, Republic of", 929 | "show_on_map": true 930 | }, 931 | "type": "Feature" 932 | }, 933 | { 934 | "geometry": { 935 | "coordinates": [ 936 | 120.21824086518218, 937 | 30.275363884214492 938 | ], 939 | "type": "Point" 940 | }, 941 | "properties": { 942 | "name": "115.236.103.113", 943 | "place": "Hangzhou, China", 944 | "show_on_map": true 945 | }, 946 | "type": "Feature" 947 | }, 948 | { 949 | "geometry": { 950 | "coordinates": [ 951 | 6.8698, 952 | 44.0483 953 | ], 954 | "type": "Point" 955 | }, 956 | "properties": { 957 | "name": "62.193.225.173", 958 | "place": "Amen, France", 959 | "show_on_map": true 960 | }, 961 | "type": "Feature" 962 | }, 963 | { 964 | "geometry": { 965 | "coordinates": [ 966 | -94.566, 967 | 39.1068 968 | ], 969 | "type": "Point" 970 | }, 971 | "properties": { 972 | "name": "50.115.166.117", 973 | "place": "Kansas City, United States", 974 | "show_on_map": true 975 | }, 976 | "type": "Feature" 977 | }, 978 | { 979 | "geometry": { 980 | "coordinates": [ 981 | 100.5014, 982 | 13.754 983 | ], 984 | "type": "Point" 985 | }, 986 | "properties": { 987 | "name": "61.19.244.118", 988 | "place": "Bangkok, Thailand", 989 | "show_on_map": true 990 | }, 991 | "type": "Feature" 992 | }, 993 | { 994 | "geometry": { 995 | "coordinates": [ 996 | 113.28585644546729, 997 | 23.08888401435725 998 | ], 999 | "type": "Point" 1000 | }, 1001 | "properties": { 1002 | "name": "202.116.1.149", 1003 | "place": "Guangzhou, China", 1004 | "show_on_map": true 1005 | }, 1006 | "type": "Feature" 1007 | }, 1008 | { 1009 | "geometry": { 1010 | "coordinates": [ 1011 | 116.4046536383238, 1012 | 39.885108455156846 1013 | ], 1014 | "type": "Point" 1015 | }, 1016 | "properties": { 1017 | "name": "117.79.148.34", 1018 | "place": "Beijing, China", 1019 | "show_on_map": true 1020 | }, 1021 | "type": "Feature" 1022 | }, 1023 | { 1024 | "geometry": { 1025 | "coordinates": [ 1026 | 113.15176409213835, 1027 | 23.074223697697537 1028 | ], 1029 | "type": "Point" 1030 | }, 1031 | "properties": { 1032 | "name": "113.107.101.234", 1033 | "place": "Guangzhou, China", 1034 | "show_on_map": true 1035 | }, 1036 | "type": "Feature" 1037 | }, 1038 | { 1039 | "geometry": { 1040 | "coordinates": [ 1041 | 116.34233993544558, 1042 | 39.991709161995544 1043 | ], 1044 | "type": "Point" 1045 | }, 1046 | "properties": { 1047 | "name": "106.3.80.36", 1048 | "place": "Beijing, China", 1049 | "show_on_map": true 1050 | }, 1051 | "type": "Feature" 1052 | }, 1053 | { 1054 | "geometry": { 1055 | "coordinates": [ 1056 | 116.31188204022322, 1057 | 40.02269100896068 1058 | ], 1059 | "type": "Point" 1060 | }, 1061 | "properties": { 1062 | "name": "166.111.230.4", 1063 | "place": "Beijing, China", 1064 | "show_on_map": true 1065 | }, 1066 | "type": "Feature" 1067 | }, 1068 | { 1069 | "geometry": { 1070 | "coordinates": [ 1071 | 116.38294682197453, 1072 | 39.85097441066661 1073 | ], 1074 | "type": "Point" 1075 | }, 1076 | "properties": { 1077 | "name": "221.176.53.74", 1078 | "place": "Beijing, China", 1079 | "show_on_map": true 1080 | }, 1081 | "type": "Feature" 1082 | }, 1083 | { 1084 | "geometry": { 1085 | "coordinates": [ 1086 | 121.31967993234666, 1087 | 31.027188693167037 1088 | ], 1089 | "type": "Point" 1090 | }, 1091 | "properties": { 1092 | "name": "61.152.172.234", 1093 | "place": "Shanghai, China", 1094 | "show_on_map": true 1095 | }, 1096 | "type": "Feature" 1097 | }, 1098 | { 1099 | "geometry": { 1100 | "coordinates": [ 1101 | 108.9286, 1102 | 34.2583 1103 | ], 1104 | "type": "Point" 1105 | }, 1106 | "properties": { 1107 | "name": "218.26.89.179", 1108 | "place": "Xian, China", 1109 | "show_on_map": true 1110 | }, 1111 | "type": "Feature" 1112 | }, 1113 | { 1114 | "geometry": { 1115 | "coordinates": [ 1116 | 118.82030888600866, 1117 | 32.06270290019252 1118 | ], 1119 | "type": "Point" 1120 | }, 1121 | "properties": { 1122 | "name": "61.155.177.58", 1123 | "place": "Nanjing, China", 1124 | "show_on_map": true 1125 | }, 1126 | "type": "Feature" 1127 | }, 1128 | { 1129 | "geometry": { 1130 | "coordinates": [ 1131 | -111.8906, 1132 | 33.6119 1133 | ], 1134 | "type": "Point" 1135 | }, 1136 | "properties": { 1137 | "name": "50.63.129.74", 1138 | "place": "Scottsdale, United States", 1139 | "show_on_map": true 1140 | }, 1141 | "type": "Feature" 1142 | }, 1143 | { 1144 | "geometry": { 1145 | "coordinates": [ 1146 | 114.27053067360241, 1147 | 30.499156272517244 1148 | ], 1149 | "type": "Point" 1150 | }, 1151 | "properties": { 1152 | "name": "219.138.72.205", 1153 | "place": "Wuhan, China", 1154 | "show_on_map": true 1155 | }, 1156 | "type": "Feature" 1157 | }, 1158 | { 1159 | "geometry": { 1160 | "coordinates": [ 1161 | 90.4086, 1162 | 23.7231 1163 | ], 1164 | "type": "Point" 1165 | }, 1166 | "properties": { 1167 | "name": "123.49.33.136", 1168 | "place": "Dhaka, Bangladesh", 1169 | "show_on_map": true 1170 | }, 1171 | "type": "Feature" 1172 | }, 1173 | { 1174 | "geometry": { 1175 | "coordinates": [ 1176 | 112.4708, 1177 | 37.7269 1178 | ], 1179 | "type": "Point" 1180 | }, 1181 | "properties": { 1182 | "name": "221.204.252.149", 1183 | "place": "Taiyuan, China", 1184 | "show_on_map": true 1185 | }, 1186 | "type": "Feature" 1187 | }, 1188 | { 1189 | "geometry": { 1190 | "coordinates": [ 1191 | -79.4167, 1192 | 43.6667 1193 | ], 1194 | "type": "Point" 1195 | }, 1196 | "properties": { 1197 | "name": "206.248.138.24", 1198 | "place": "Toronto, Canada", 1199 | "show_on_map": true 1200 | }, 1201 | "type": "Feature" 1202 | }, 1203 | { 1204 | "geometry": { 1205 | "coordinates": [ 1206 | 121.32569168306382, 1207 | 30.97726007085732 1208 | ], 1209 | "type": "Point" 1210 | }, 1211 | "properties": { 1212 | "name": "180.153.224.106", 1213 | "place": "Shanghai, China", 1214 | "show_on_map": true 1215 | }, 1216 | "type": "Feature" 1217 | }, 1218 | { 1219 | "geometry": { 1220 | "coordinates": [ 1221 | 72.8258, 1222 | 18.975 1223 | ], 1224 | "type": "Point" 1225 | }, 1226 | "properties": { 1227 | "name": "220.226.198.215", 1228 | "place": "Mumbai, India", 1229 | "show_on_map": true 1230 | }, 1231 | "type": "Feature" 1232 | }, 1233 | { 1234 | "geometry": { 1235 | "coordinates": [ 1236 | -117.9055, 1237 | 33.6793 1238 | ], 1239 | "type": "Point" 1240 | }, 1241 | "properties": { 1242 | "name": "74.11.140.170", 1243 | "place": "Costa Mesa, United States", 1244 | "show_on_map": true 1245 | }, 1246 | "type": "Feature" 1247 | }, 1248 | { 1249 | "geometry": { 1250 | "coordinates": [ 1251 | -0.0931, 1252 | 51.5142 1253 | ], 1254 | "type": "Point" 1255 | }, 1256 | "properties": { 1257 | "name": "31.3.245.178", 1258 | "place": "London, United Kingdom", 1259 | "show_on_map": true 1260 | }, 1261 | "type": "Feature" 1262 | }, 1263 | { 1264 | "geometry": { 1265 | "coordinates": [ 1266 | 116.42077930191212, 1267 | 40.02132809301251 1268 | ], 1269 | "type": "Point" 1270 | }, 1271 | "properties": { 1272 | "name": "123.126.30.26", 1273 | "place": "Beijing, China", 1274 | "show_on_map": true 1275 | }, 1276 | "type": "Feature" 1277 | }, 1278 | { 1279 | "geometry": { 1280 | "coordinates": [ 1281 | 113.24791772637357, 1282 | 23.054190566093663 1283 | ], 1284 | "type": "Point" 1285 | }, 1286 | "properties": { 1287 | "name": "59.39.183.67", 1288 | "place": "Guangzhou, China", 1289 | "show_on_map": true 1290 | }, 1291 | "type": "Feature" 1292 | }, 1293 | { 1294 | "geometry": { 1295 | "coordinates": [ 1296 | -123.343, 1297 | 44.4698 1298 | ], 1299 | "type": "Point" 1300 | }, 1301 | "properties": { 1302 | "name": "69.59.197.206", 1303 | "place": "Corvallis, United States", 1304 | "show_on_map": true 1305 | }, 1306 | "type": "Feature" 1307 | }, 1308 | { 1309 | "geometry": { 1310 | "coordinates": [ 1311 | 127.4197, 1312 | 36.3214 1313 | ], 1314 | "type": "Point" 1315 | }, 1316 | "properties": { 1317 | "name": "203.231.233.18", 1318 | "place": "Daejeon, Korea, Republic of", 1319 | "show_on_map": true 1320 | }, 1321 | "type": "Feature" 1322 | }, 1323 | { 1324 | "geometry": { 1325 | "coordinates": [ 1326 | -4.7237, 1327 | 41.6552 1328 | ], 1329 | "type": "Point" 1330 | }, 1331 | "properties": { 1332 | "name": "89.140.85.139", 1333 | "place": "Valladolid, Spain", 1334 | "show_on_map": true 1335 | }, 1336 | "type": "Feature" 1337 | }, 1338 | { 1339 | "geometry": { 1340 | "coordinates": [ 1341 | 139.4039, 1342 | 35.3261 1343 | ], 1344 | "type": "Point" 1345 | }, 1346 | "properties": { 1347 | "name": "111.104.75.90", 1348 | "place": "Chigasaki, Japan", 1349 | "show_on_map": true 1350 | }, 1351 | "type": "Feature" 1352 | }, 1353 | { 1354 | "geometry": { 1355 | "coordinates": [ 1356 | 126.91653756620242, 1357 | 37.57408851763418 1358 | ], 1359 | "type": "Point" 1360 | }, 1361 | "properties": { 1362 | "name": "58.225.75.228", 1363 | "place": "Seoul, Korea, Republic of", 1364 | "show_on_map": true 1365 | }, 1366 | "type": "Feature" 1367 | }, 1368 | { 1369 | "geometry": { 1370 | "coordinates": [ 1371 | 118.83240040322819, 1372 | 32.03311088313591 1373 | ], 1374 | "type": "Point" 1375 | }, 1376 | "properties": { 1377 | "name": "61.132.4.85", 1378 | "place": "Nanjing, China", 1379 | "show_on_map": true 1380 | }, 1381 | "type": "Feature" 1382 | }, 1383 | { 1384 | "geometry": { 1385 | "coordinates": [ 1386 | -73.5833, 1387 | 45.5 1388 | ], 1389 | "type": "Point" 1390 | }, 1391 | "properties": { 1392 | "name": "199.201.121.145", 1393 | "place": "Montreal, Canada", 1394 | "show_on_map": true 1395 | }, 1396 | "type": "Feature" 1397 | }, 1398 | { 1399 | "geometry": { 1400 | "coordinates": [ 1401 | 118.69275120209757, 1402 | 32.03902429820648 1403 | ], 1404 | "type": "Point" 1405 | }, 1406 | "properties": { 1407 | "name": "222.186.25.35", 1408 | "place": "Nanjing, China", 1409 | "show_on_map": true 1410 | }, 1411 | "type": "Feature" 1412 | }, 1413 | { 1414 | "geometry": { 1415 | "coordinates": [ 1416 | 116.48169885462691, 1417 | 39.90652183869377 1418 | ], 1419 | "type": "Point" 1420 | }, 1421 | "properties": { 1422 | "name": "222.59.10.6", 1423 | "place": "Beijing, China", 1424 | "show_on_map": true 1425 | }, 1426 | "type": "Feature" 1427 | }, 1428 | { 1429 | "geometry": { 1430 | "coordinates": [ 1431 | -99.0986643338024, 1432 | 19.357234448405002 1433 | ], 1434 | "type": "Point" 1435 | }, 1436 | "properties": { 1437 | "name": "201.175.38.183", 1438 | "place": "Mexico, Mexico", 1439 | "show_on_map": true 1440 | }, 1441 | "type": "Feature" 1442 | }, 1443 | { 1444 | "geometry": { 1445 | "coordinates": [ 1446 | 116.9972, 1447 | 36.6683 1448 | ], 1449 | "type": "Point" 1450 | }, 1451 | "properties": { 1452 | "name": "210.44.159.49", 1453 | "place": "Jinan, China", 1454 | "show_on_map": true 1455 | }, 1456 | "type": "Feature" 1457 | }, 1458 | { 1459 | "geometry": { 1460 | "coordinates": [ 1461 | 103.7922, 1462 | 36.0564 1463 | ], 1464 | "type": "Point" 1465 | }, 1466 | "properties": { 1467 | "name": "218.203.215.210", 1468 | "place": "Lanzhou, China", 1469 | "show_on_map": true 1470 | }, 1471 | "type": "Feature" 1472 | }, 1473 | { 1474 | "geometry": { 1475 | "coordinates": [ 1476 | -73.60153456294782, 1477 | 45.40905920936227 1478 | ], 1479 | "type": "Point" 1480 | }, 1481 | "properties": { 1482 | "name": "64.15.152.208", 1483 | "place": "Montreal, Canada", 1484 | "show_on_map": true 1485 | }, 1486 | "type": "Feature" 1487 | }, 1488 | { 1489 | "geometry": { 1490 | "coordinates": [ 1491 | 110.3417, 1492 | 20.0458 1493 | ], 1494 | "type": "Point" 1495 | }, 1496 | "properties": { 1497 | "name": "220.174.236.210", 1498 | "place": "Haikou, China", 1499 | "show_on_map": true 1500 | }, 1501 | "type": "Feature" 1502 | }, 1503 | { 1504 | "geometry": { 1505 | "coordinates": [ 1506 | 126.89413775413806, 1507 | 37.54543734089498 1508 | ], 1509 | "type": "Point" 1510 | }, 1511 | "properties": { 1512 | "name": "1.214.35.220", 1513 | "place": "Seoul, Korea, Republic of", 1514 | "show_on_map": true 1515 | }, 1516 | "type": "Feature" 1517 | }, 1518 | { 1519 | "geometry": { 1520 | "coordinates": [ 1521 | -74.0628, 1522 | 4.6492 1523 | ], 1524 | "type": "Point" 1525 | }, 1526 | "properties": { 1527 | "name": "190.144.87.109", 1528 | "place": "Bogot\u00e1, Colombia", 1529 | "show_on_map": true 1530 | }, 1531 | "type": "Feature" 1532 | }, 1533 | { 1534 | "geometry": { 1535 | "coordinates": [ 1536 | 37.5331, 1537 | 47.1057 1538 | ], 1539 | "type": "Point" 1540 | }, 1541 | "properties": { 1542 | "name": "91.243.0.93", 1543 | "place": "Mariupol, Ukraine", 1544 | "show_on_map": true 1545 | }, 1546 | "type": "Feature" 1547 | }, 1548 | { 1549 | "geometry": { 1550 | "coordinates": [ 1551 | 13.4, 1552 | 52.5167 1553 | ], 1554 | "type": "Point" 1555 | }, 1556 | "properties": { 1557 | "name": "85.214.194.124", 1558 | "place": "Berlin, Germany", 1559 | "show_on_map": true 1560 | }, 1561 | "type": "Feature" 1562 | }, 1563 | { 1564 | "geometry": { 1565 | "coordinates": [ 1566 | 99.4928, 1567 | 18.2923 1568 | ], 1569 | "type": "Point" 1570 | }, 1571 | "properties": { 1572 | "name": "61.7.231.146", 1573 | "place": "Lampang, Thailand", 1574 | "show_on_map": true 1575 | }, 1576 | "type": "Feature" 1577 | }, 1578 | { 1579 | "geometry": { 1580 | "coordinates": [ 1581 | -79.43318093748698, 1582 | 43.725354154570844 1583 | ], 1584 | "type": "Point" 1585 | }, 1586 | "properties": { 1587 | "name": "96.45.196.205", 1588 | "place": "Toronto, Canada", 1589 | "show_on_map": true 1590 | }, 1591 | "type": "Feature" 1592 | }, 1593 | { 1594 | "geometry": { 1595 | "coordinates": [ 1596 | 116.96838015997847, 1597 | 36.645045382264314 1598 | ], 1599 | "type": "Point" 1600 | }, 1601 | "properties": { 1602 | "name": "218.59.215.185", 1603 | "place": "Jinan, China", 1604 | "show_on_map": true 1605 | }, 1606 | "type": "Feature" 1607 | }, 1608 | { 1609 | "geometry": { 1610 | "coordinates": [ 1611 | -73.64292750765175, 1612 | 45.476294476499525 1613 | ], 1614 | "type": "Point" 1615 | }, 1616 | "properties": { 1617 | "name": "192.95.6.137", 1618 | "place": "Montreal, Canada", 1619 | "show_on_map": true 1620 | }, 1621 | "type": "Feature" 1622 | }, 1623 | { 1624 | "geometry": { 1625 | "coordinates": [ 1626 | 139.7514, 1627 | 35.685 1628 | ], 1629 | "type": "Point" 1630 | }, 1631 | "properties": { 1632 | "name": "113.43.112.67", 1633 | "place": "Tokyo, Japan", 1634 | "show_on_map": true 1635 | }, 1636 | "type": "Feature" 1637 | }, 1638 | { 1639 | "geometry": { 1640 | "coordinates": [ 1641 | -79.7667, 1642 | 43.6833 1643 | ], 1644 | "type": "Point" 1645 | }, 1646 | "properties": { 1647 | "name": "207.245.4.132", 1648 | "place": "Brampton, Canada", 1649 | "show_on_map": true 1650 | }, 1651 | "type": "Feature" 1652 | }, 1653 | { 1654 | "geometry": { 1655 | "coordinates": [ 1656 | 114.1333, 1657 | 22.5333 1658 | ], 1659 | "type": "Point" 1660 | }, 1661 | "properties": { 1662 | "name": "121.15.6.198", 1663 | "place": "Shenzhen, China", 1664 | "show_on_map": true 1665 | }, 1666 | "type": "Feature" 1667 | }, 1668 | { 1669 | "geometry": { 1670 | "coordinates": [ 1671 | -80.3017, 1672 | 25.8338 1673 | ], 1674 | "type": "Point" 1675 | }, 1676 | "properties": { 1677 | "name": "198.154.63.158", 1678 | "place": "Miami, United States", 1679 | "show_on_map": true 1680 | }, 1681 | "type": "Feature" 1682 | }, 1683 | { 1684 | "geometry": { 1685 | "coordinates": [ 1686 | 104.04646257066953, 1687 | 30.620850122367997 1688 | ], 1689 | "type": "Point" 1690 | }, 1691 | "properties": { 1692 | "name": "182.131.22.235", 1693 | "place": "Chengdu, China", 1694 | "show_on_map": true 1695 | }, 1696 | "type": "Feature" 1697 | }, 1698 | { 1699 | "geometry": { 1700 | "coordinates": [ 1701 | 120.24648661894868, 1702 | 30.29940620469628 1703 | ], 1704 | "type": "Point" 1705 | }, 1706 | "properties": { 1707 | "name": "210.83.84.27", 1708 | "place": "Hangzhou, China", 1709 | "show_on_map": true 1710 | }, 1711 | "type": "Feature" 1712 | }, 1713 | { 1714 | "geometry": { 1715 | "coordinates": [ 1716 | -57.6667, 1717 | -25.2667 1718 | ], 1719 | "type": "Point" 1720 | }, 1721 | "properties": { 1722 | "name": "190.211.243.82", 1723 | "place": "Asunci\u00f3n, Paraguay", 1724 | "show_on_map": true 1725 | }, 1726 | "type": "Feature" 1727 | }, 1728 | { 1729 | "geometry": { 1730 | "coordinates": [ 1731 | 4.7249, 1732 | 49.7685 1733 | ], 1734 | "type": "Point" 1735 | }, 1736 | "properties": { 1737 | "name": "92.131.229.25", 1738 | "place": "Charleville-m\u00e9zi\u00e8res, France", 1739 | "show_on_map": true 1740 | }, 1741 | "type": "Feature" 1742 | }, 1743 | { 1744 | "geometry": { 1745 | "coordinates": [ 1746 | -77.05, 1747 | -12.05 1748 | ], 1749 | "type": "Point" 1750 | }, 1751 | "properties": { 1752 | "name": "201.234.126.70", 1753 | "place": "Lima, Peru", 1754 | "show_on_map": true 1755 | }, 1756 | "type": "Feature" 1757 | }, 1758 | { 1759 | "geometry": { 1760 | "coordinates": [ 1761 | 116.32917191769496, 1762 | 39.91047828317481 1763 | ], 1764 | "type": "Point" 1765 | }, 1766 | "properties": { 1767 | "name": "211.167.103.172", 1768 | "place": "Beijing, China", 1769 | "show_on_map": true 1770 | }, 1771 | "type": "Feature" 1772 | }, 1773 | { 1774 | "geometry": { 1775 | "coordinates": [ 1776 | 78.4744, 1777 | 17.3753 1778 | ], 1779 | "type": "Point" 1780 | }, 1781 | "properties": { 1782 | "name": "202.63.138.118", 1783 | "place": "Hyderabad, India", 1784 | "show_on_map": true 1785 | }, 1786 | "type": "Feature" 1787 | }, 1788 | { 1789 | "geometry": { 1790 | "coordinates": [ 1791 | 116.45404369899013, 1792 | 40.01829804760104 1793 | ], 1794 | "type": "Point" 1795 | }, 1796 | "properties": { 1797 | "name": "211.95.73.50", 1798 | "place": "Beijing, China", 1799 | "show_on_map": true 1800 | }, 1801 | "type": "Feature" 1802 | }, 1803 | { 1804 | "geometry": { 1805 | "coordinates": [ 1806 | -112.0717, 1807 | 33.5083 1808 | ], 1809 | "type": "Point" 1810 | }, 1811 | "properties": { 1812 | "name": "216.19.223.55", 1813 | "place": "Phoenix, United States", 1814 | "show_on_map": true 1815 | }, 1816 | "type": "Feature" 1817 | }, 1818 | { 1819 | "geometry": { 1820 | "coordinates": [ 1821 | 116.43888386543811, 1822 | 39.91402666664927 1823 | ], 1824 | "type": "Point" 1825 | }, 1826 | "properties": { 1827 | "name": "211.95.76.242", 1828 | "place": "Beijing, China", 1829 | "show_on_map": true 1830 | }, 1831 | "type": "Feature" 1832 | }, 1833 | { 1834 | "geometry": { 1835 | "coordinates": [ 1836 | 116.41715813267798, 1837 | 39.843877552462196 1838 | ], 1839 | "type": "Point" 1840 | }, 1841 | "properties": { 1842 | "name": "120.202.67.84", 1843 | "place": "Beijing, China", 1844 | "show_on_map": true 1845 | }, 1846 | "type": "Feature" 1847 | }, 1848 | { 1849 | "geometry": { 1850 | "coordinates": [ 1851 | 87.6005, 1852 | 43.801 1853 | ], 1854 | "type": "Point" 1855 | }, 1856 | "properties": { 1857 | "name": "222.80.184.46", 1858 | "place": "\u00dcr\u00fcmqi, China", 1859 | "show_on_map": true 1860 | }, 1861 | "type": "Feature" 1862 | }, 1863 | { 1864 | "geometry": { 1865 | "coordinates": [ 1866 | 116.4738251001876, 1867 | 39.841891253019796 1868 | ], 1869 | "type": "Point" 1870 | }, 1871 | "properties": { 1872 | "name": "183.238.93.39", 1873 | "place": "Beijing, China", 1874 | "show_on_map": true 1875 | }, 1876 | "type": "Feature" 1877 | }, 1878 | { 1879 | "geometry": { 1880 | "coordinates": [ 1881 | 118.70422777391711, 1882 | 32.0239004470264 1883 | ], 1884 | "type": "Point" 1885 | }, 1886 | "properties": { 1887 | "name": "222.186.31.83", 1888 | "place": "Nanjing, China", 1889 | "show_on_map": true 1890 | }, 1891 | "type": "Feature" 1892 | }, 1893 | { 1894 | "geometry": { 1895 | "coordinates": [ 1896 | -80.1273, 1897 | 26.1209 1898 | ], 1899 | "type": "Point" 1900 | }, 1901 | "properties": { 1902 | "name": "65.111.174.19", 1903 | "place": "Fort Lauderdale, United States", 1904 | "show_on_map": true 1905 | }, 1906 | "type": "Feature" 1907 | }, 1908 | { 1909 | "geometry": { 1910 | "coordinates": [ 1911 | 114.29954336852687, 1912 | 30.518040058879485 1913 | ], 1914 | "type": "Point" 1915 | }, 1916 | "properties": { 1917 | "name": "61.183.35.171", 1918 | "place": "Wuhan, China", 1919 | "show_on_map": true 1920 | }, 1921 | "type": "Feature" 1922 | }, 1923 | { 1924 | "geometry": { 1925 | "coordinates": [ 1926 | 114.34045687255028, 1927 | 30.578679501904855 1928 | ], 1929 | "type": "Point" 1930 | }, 1931 | "properties": { 1932 | "name": "58.51.91.54", 1933 | "place": "Wuhan, China", 1934 | "show_on_map": true 1935 | }, 1936 | "type": "Feature" 1937 | }, 1938 | { 1939 | "geometry": { 1940 | "coordinates": [ 1941 | 121.0, 1942 | 23.5 1943 | ], 1944 | "type": "Point" 1945 | }, 1946 | "properties": { 1947 | "name": "203.69.37.206", 1948 | "place": ", Taiwan", 1949 | "show_on_map": true 1950 | }, 1951 | "type": "Feature" 1952 | }, 1953 | { 1954 | "geometry": { 1955 | "coordinates": [ 1956 | 35.0, 1957 | 39.0 1958 | ], 1959 | "type": "Point" 1960 | }, 1961 | "properties": { 1962 | "name": "94.102.3.230", 1963 | "place": ", Turkey", 1964 | "show_on_map": true 1965 | }, 1966 | "type": "Feature" 1967 | }, 1968 | { 1969 | "geometry": { 1970 | "coordinates": [ 1971 | 117.1767, 1972 | 39.1422 1973 | ], 1974 | "type": "Point" 1975 | }, 1976 | "properties": { 1977 | "name": "60.28.27.14", 1978 | "place": "Tianjin, China", 1979 | "show_on_map": true 1980 | }, 1981 | "type": "Feature" 1982 | }, 1983 | { 1984 | "geometry": { 1985 | "coordinates": [ 1986 | 68.0, 1987 | 48.0 1988 | ], 1989 | "type": "Point" 1990 | }, 1991 | "properties": { 1992 | "name": "82.200.253.82", 1993 | "place": ", Kazakhstan", 1994 | "show_on_map": true 1995 | }, 1996 | "type": "Feature" 1997 | }, 1998 | { 1999 | "geometry": { 2000 | "coordinates": [ 2001 | 121.38942337428283, 2002 | 31.12798342661126 2003 | ], 2004 | "type": "Point" 2005 | }, 2006 | "properties": { 2007 | "name": "101.44.1.135", 2008 | "place": "Shanghai, China", 2009 | "show_on_map": true 2010 | }, 2011 | "type": "Feature" 2012 | }, 2013 | { 2014 | "geometry": { 2015 | "coordinates": [ 2016 | 4.9167, 2017 | 52.35 2018 | ], 2019 | "type": "Point" 2020 | }, 2021 | "properties": { 2022 | "name": "85.17.248.77", 2023 | "place": "Amsterdam, Netherlands", 2024 | "show_on_map": true 2025 | }, 2026 | "type": "Feature" 2027 | }, 2028 | { 2029 | "geometry": { 2030 | "coordinates": [ 2031 | 121.34487088736282, 2032 | 31.038395272404255 2033 | ], 2034 | "type": "Point" 2035 | }, 2036 | "properties": { 2037 | "name": "124.202.145.10", 2038 | "place": "Shanghai, China", 2039 | "show_on_map": true 2040 | }, 2041 | "type": "Feature" 2042 | }, 2043 | { 2044 | "geometry": { 2045 | "coordinates": [ 2046 | 127.5, 2047 | 37.0 2048 | ], 2049 | "type": "Point" 2050 | }, 2051 | "properties": { 2052 | "name": "211.48.41.124", 2053 | "place": ", Korea, Republic of", 2054 | "show_on_map": true 2055 | }, 2056 | "type": "Feature" 2057 | }, 2058 | { 2059 | "geometry": { 2060 | "coordinates": [ 2061 | 117.16460407892704, 2062 | 39.04280646436162 2063 | ], 2064 | "type": "Point" 2065 | }, 2066 | "properties": { 2067 | "name": "221.238.237.45", 2068 | "place": "Tianjin, China", 2069 | "show_on_map": true 2070 | }, 2071 | "type": "Feature" 2072 | }, 2073 | { 2074 | "geometry": { 2075 | "coordinates": [ 2076 | 105.0, 2077 | 35.0 2078 | ], 2079 | "type": "Point" 2080 | }, 2081 | "properties": { 2082 | "name": "210.30.1.19", 2083 | "place": ", China", 2084 | "show_on_map": true 2085 | }, 2086 | "type": "Feature" 2087 | }, 2088 | { 2089 | "geometry": { 2090 | "coordinates": [ 2091 | 117.2808, 2092 | 31.8639 2093 | ], 2094 | "type": "Point" 2095 | }, 2096 | "properties": { 2097 | "name": "60.173.26.75", 2098 | "place": "Hefei, China", 2099 | "show_on_map": true 2100 | }, 2101 | "type": "Feature" 2102 | }, 2103 | { 2104 | "geometry": { 2105 | "coordinates": [ 2106 | 31.25, 2107 | 30.05 2108 | ], 2109 | "type": "Point" 2110 | }, 2111 | "properties": { 2112 | "name": "41.130.116.247", 2113 | "place": "Cairo, Egypt", 2114 | "show_on_map": true 2115 | }, 2116 | "type": "Feature" 2117 | }, 2118 | { 2119 | "geometry": { 2120 | "coordinates": [ 2121 | 73.0551, 2122 | 33.69 2123 | ], 2124 | "type": "Point" 2125 | }, 2126 | "properties": { 2127 | "name": "182.184.0.141", 2128 | "place": "Islamabad, Pakistan", 2129 | "show_on_map": true 2130 | }, 2131 | "type": "Feature" 2132 | }, 2133 | { 2134 | "geometry": { 2135 | "coordinates": [ 2136 | 104.03162665923982, 2137 | 30.71216393762488 2138 | ], 2139 | "type": "Point" 2140 | }, 2141 | "properties": { 2142 | "name": "182.140.241.193", 2143 | "place": "Chengdu, China", 2144 | "show_on_map": true 2145 | }, 2146 | "type": "Feature" 2147 | }, 2148 | { 2149 | "geometry": { 2150 | "coordinates": [ 2151 | 102.7183, 2152 | 25.0389 2153 | ], 2154 | "type": "Point" 2155 | }, 2156 | "properties": { 2157 | "name": "220.164.144.135", 2158 | "place": "Kunming, China", 2159 | "show_on_map": true 2160 | }, 2161 | "type": "Feature" 2162 | }, 2163 | { 2164 | "geometry": { 2165 | "coordinates": [ 2166 | 9.0, 2167 | 51.0 2168 | ], 2169 | "type": "Point" 2170 | }, 2171 | "properties": { 2172 | "name": "88.198.214.232", 2173 | "place": ", Germany", 2174 | "show_on_map": true 2175 | }, 2176 | "type": "Feature" 2177 | }, 2178 | { 2179 | "geometry": { 2180 | "coordinates": [ 2181 | 120.08980819562451, 2182 | 30.37822414692648 2183 | ], 2184 | "type": "Point" 2185 | }, 2186 | "properties": { 2187 | "name": "125.210.221.161", 2188 | "place": "Hangzhou, China", 2189 | "show_on_map": true 2190 | }, 2191 | "type": "Feature" 2192 | }, 2193 | { 2194 | "geometry": { 2195 | "coordinates": [ 2196 | 104.92026630953134, 2197 | 35.00643871748583 2198 | ], 2199 | "type": "Point" 2200 | }, 2201 | "properties": { 2202 | "name": "183.232.32.24", 2203 | "place": ", China", 2204 | "show_on_map": true 2205 | }, 2206 | "type": "Feature" 2207 | }, 2208 | { 2209 | "geometry": { 2210 | "coordinates": [ 2211 | 117.0226489142994, 2212 | 36.67785607436937 2213 | ], 2214 | "type": "Point" 2215 | }, 2216 | "properties": { 2217 | "name": "61.156.238.56", 2218 | "place": "Jinan, China", 2219 | "show_on_map": true 2220 | }, 2221 | "type": "Feature" 2222 | }, 2223 | { 2224 | "geometry": { 2225 | "coordinates": [ 2226 | -18.0, 2227 | 65.0 2228 | ], 2229 | "type": "Point" 2230 | }, 2231 | "properties": { 2232 | "name": "82.221.102.199", 2233 | "place": ", Iceland", 2234 | "show_on_map": true 2235 | }, 2236 | "type": "Feature" 2237 | }, 2238 | { 2239 | "geometry": { 2240 | "coordinates": [ 2241 | 114.8865, 2242 | 30.3961 2243 | ], 2244 | "type": "Point" 2245 | }, 2246 | "properties": { 2247 | "name": "219.138.203.198", 2248 | "place": "Ezhou, China", 2249 | "show_on_map": true 2250 | }, 2251 | "type": "Feature" 2252 | }, 2253 | { 2254 | "geometry": { 2255 | "coordinates": [ 2256 | 113.18016199959902, 2257 | 23.189626776170666 2258 | ], 2259 | "type": "Point" 2260 | }, 2261 | "properties": { 2262 | "name": "119.134.244.22", 2263 | "place": "Guangzhou, China", 2264 | "show_on_map": true 2265 | }, 2266 | "type": "Feature" 2267 | }, 2268 | { 2269 | "geometry": { 2270 | "coordinates": [ 2271 | -61.5167, 2272 | 10.65 2273 | ], 2274 | "type": "Point" 2275 | }, 2276 | "properties": { 2277 | "name": "196.3.132.18", 2278 | "place": "Port-of-spain, Trinidad and Tobago", 2279 | "show_on_map": true 2280 | }, 2281 | "type": "Feature" 2282 | }, 2283 | { 2284 | "geometry": { 2285 | "coordinates": [ 2286 | 105.85, 2287 | 21.0333 2288 | ], 2289 | "type": "Point" 2290 | }, 2291 | "properties": { 2292 | "name": "123.30.105.115", 2293 | "place": "Hanoi, Vietnam", 2294 | "show_on_map": true 2295 | }, 2296 | "type": "Feature" 2297 | }, 2298 | { 2299 | "geometry": { 2300 | "coordinates": [ 2301 | 118.69448403405322, 2302 | 32.00419604018864 2303 | ], 2304 | "type": "Point" 2305 | }, 2306 | "properties": { 2307 | "name": "61.155.150.217", 2308 | "place": "Nanjing, China", 2309 | "show_on_map": true 2310 | }, 2311 | "type": "Feature" 2312 | }, 2313 | { 2314 | "geometry": { 2315 | "coordinates": [ 2316 | 118.78600537152502, 2317 | 32.15582806973238 2318 | ], 2319 | "type": "Point" 2320 | }, 2321 | "properties": { 2322 | "name": "58.240.180.108", 2323 | "place": "Nanjing, China", 2324 | "show_on_map": true 2325 | }, 2326 | "type": "Feature" 2327 | }, 2328 | { 2329 | "geometry": { 2330 | "coordinates": [ 2331 | 113.30550982984265, 2332 | 23.211259516990786 2333 | ], 2334 | "type": "Point" 2335 | }, 2336 | "properties": { 2337 | "name": "183.60.20.35", 2338 | "place": "Guangzhou, China", 2339 | "show_on_map": true 2340 | }, 2341 | "type": "Feature" 2342 | }, 2343 | { 2344 | "geometry": { 2345 | "coordinates": [ 2346 | 116.36511467705607, 2347 | 39.99631925953655 2348 | ], 2349 | "type": "Point" 2350 | }, 2351 | "properties": { 2352 | "name": "223.5.3.200", 2353 | "place": "Beijing, China", 2354 | "show_on_map": true 2355 | }, 2356 | "type": "Feature" 2357 | }, 2358 | { 2359 | "geometry": { 2360 | "coordinates": [ 2361 | 8.0, 2362 | 47.0 2363 | ], 2364 | "type": "Point" 2365 | }, 2366 | "properties": { 2367 | "name": "185.12.45.26", 2368 | "place": ", Switzerland", 2369 | "show_on_map": true 2370 | }, 2371 | "type": "Feature" 2372 | }, 2373 | { 2374 | "geometry": { 2375 | "coordinates": [ 2376 | 120.07081848302604, 2377 | 30.201125776241465 2378 | ], 2379 | "type": "Point" 2380 | }, 2381 | "properties": { 2382 | "name": "115.238.73.16", 2383 | "place": "Hangzhou, China", 2384 | "show_on_map": true 2385 | }, 2386 | "type": "Feature" 2387 | }, 2388 | { 2389 | "geometry": { 2390 | "coordinates": [ 2391 | 116.39795533368076, 2392 | 39.95975363474229 2393 | ], 2394 | "type": "Point" 2395 | }, 2396 | "properties": { 2397 | "name": "219.239.26.9", 2398 | "place": "Beijing, China", 2399 | "show_on_map": true 2400 | }, 2401 | "type": "Feature" 2402 | }, 2403 | { 2404 | "geometry": { 2405 | "coordinates": [ 2406 | 127.7342, 2407 | 37.8747 2408 | ], 2409 | "type": "Point" 2410 | }, 2411 | "properties": { 2412 | "name": "211.55.42.92", 2413 | "place": "Chuncheon, Korea, Republic of", 2414 | "show_on_map": true 2415 | }, 2416 | "type": "Feature" 2417 | }, 2418 | { 2419 | "geometry": { 2420 | "coordinates": [ 2421 | -46.56665835878514, 2422 | -23.448680949994692 2423 | ], 2424 | "type": "Point" 2425 | }, 2426 | "properties": { 2427 | "name": "201.20.233.228", 2428 | "place": "S\u00e3o Paulo, Brazil", 2429 | "show_on_map": true 2430 | }, 2431 | "type": "Feature" 2432 | }, 2433 | { 2434 | "geometry": { 2435 | "coordinates": [ 2436 | 113.58006755708185, 2437 | 34.718594529492925 2438 | ], 2439 | "type": "Point" 2440 | }, 2441 | "properties": { 2442 | "name": "125.46.13.163", 2443 | "place": "Zhengzhou, China", 2444 | "show_on_map": true 2445 | }, 2446 | "type": "Feature" 2447 | }, 2448 | { 2449 | "geometry": { 2450 | "coordinates": [ 2451 | -48.65, 2452 | -26.8833 2453 | ], 2454 | "type": "Point" 2455 | }, 2456 | "properties": { 2457 | "name": "201.49.111.50", 2458 | "place": "Itaja\u00ed, Brazil", 2459 | "show_on_map": true 2460 | }, 2461 | "type": "Feature" 2462 | }, 2463 | { 2464 | "geometry": { 2465 | "coordinates": [ 2466 | 121.48933907254072, 2467 | 31.01676805159455 2468 | ], 2469 | "type": "Point" 2470 | }, 2471 | "properties": { 2472 | "name": "180.153.255.233", 2473 | "place": "Shanghai, China", 2474 | "show_on_map": true 2475 | }, 2476 | "type": "Feature" 2477 | }, 2478 | { 2479 | "geometry": { 2480 | "coordinates": [ 2481 | 127.05182118123558, 2482 | 37.584719118538665 2483 | ], 2484 | "type": "Point" 2485 | }, 2486 | "properties": { 2487 | "name": "123.215.230.170", 2488 | "place": "Seoul, Korea, Republic of", 2489 | "show_on_map": true 2490 | }, 2491 | "type": "Feature" 2492 | }, 2493 | { 2494 | "geometry": { 2495 | "coordinates": [ 2496 | 123.4328, 2497 | 41.7922 2498 | ], 2499 | "type": "Point" 2500 | }, 2501 | "properties": { 2502 | "name": "218.61.139.114", 2503 | "place": "Shenyang, China", 2504 | "show_on_map": true 2505 | }, 2506 | "type": "Feature" 2507 | }, 2508 | { 2509 | "geometry": { 2510 | "coordinates": [ 2511 | -72.0, 2512 | 4.0 2513 | ], 2514 | "type": "Point" 2515 | }, 2516 | "properties": { 2517 | "name": "190.24.10.11", 2518 | "place": ", Colombia", 2519 | "show_on_map": true 2520 | }, 2521 | "type": "Feature" 2522 | }, 2523 | { 2524 | "geometry": { 2525 | "coordinates": [ 2526 | 120.06141293077535, 2527 | 30.252175320379123 2528 | ], 2529 | "type": "Point" 2530 | }, 2531 | "properties": { 2532 | "name": "42.121.114.23", 2533 | "place": "Hangzhou, China", 2534 | "show_on_map": true 2535 | }, 2536 | "type": "Feature" 2537 | }, 2538 | { 2539 | "geometry": { 2540 | "coordinates": [ 2541 | 90.0, 2542 | 24.0 2543 | ], 2544 | "type": "Point" 2545 | }, 2546 | "properties": { 2547 | "name": "119.148.2.19", 2548 | "place": ", Bangladesh", 2549 | "show_on_map": true 2550 | }, 2551 | "type": "Feature" 2552 | }, 2553 | { 2554 | "geometry": { 2555 | "coordinates": [ 2556 | 116.37749104247978, 2557 | 39.876830254611605 2558 | ], 2559 | "type": "Point" 2560 | }, 2561 | "properties": { 2562 | "name": "119.254.67.206", 2563 | "place": "Beijing, China", 2564 | "show_on_map": true 2565 | }, 2566 | "type": "Feature" 2567 | }, 2568 | { 2569 | "geometry": { 2570 | "coordinates": [ 2571 | 5.75, 2572 | 52.5 2573 | ], 2574 | "type": "Point" 2575 | }, 2576 | "properties": { 2577 | "name": "212.7.210.133", 2578 | "place": ", Netherlands", 2579 | "show_on_map": true 2580 | }, 2581 | "type": "Feature" 2582 | }, 2583 | { 2584 | "geometry": { 2585 | "coordinates": [ 2586 | 120.14640523568288, 2587 | 30.288691898392305 2588 | ], 2589 | "type": "Point" 2590 | }, 2591 | "properties": { 2592 | "name": "115.236.185.75", 2593 | "place": "Hangzhou, China", 2594 | "show_on_map": true 2595 | }, 2596 | "type": "Feature" 2597 | }, 2598 | { 2599 | "geometry": { 2600 | "coordinates": [ 2601 | 100.0, 2602 | 15.0 2603 | ], 2604 | "type": "Point" 2605 | }, 2606 | "properties": { 2607 | "name": "202.172.17.70", 2608 | "place": ", Thailand", 2609 | "show_on_map": true 2610 | }, 2611 | "type": "Feature" 2612 | }, 2613 | { 2614 | "geometry": { 2615 | "coordinates": [ 2616 | 116.93487002201589, 2617 | 36.667205286088404 2618 | ], 2619 | "type": "Point" 2620 | }, 2621 | "properties": { 2622 | "name": "60.216.101.120", 2623 | "place": "Jinan, China", 2624 | "show_on_map": true 2625 | }, 2626 | "type": "Feature" 2627 | }, 2628 | { 2629 | "geometry": { 2630 | "coordinates": [ 2631 | 114.28233027446366, 2632 | 30.481171370665468 2633 | ], 2634 | "type": "Point" 2635 | }, 2636 | "properties": { 2637 | "name": "61.136.171.198", 2638 | "place": "Wuhan, China", 2639 | "show_on_map": true 2640 | }, 2641 | "type": "Feature" 2642 | }, 2643 | { 2644 | "geometry": { 2645 | "coordinates": [ 2646 | 121.33870064715428, 2647 | 30.96332749970407 2648 | ], 2649 | "type": "Point" 2650 | }, 2651 | "properties": { 2652 | "name": "203.156.198.228", 2653 | "place": "Shanghai, China", 2654 | "show_on_map": true 2655 | }, 2656 | "type": "Feature" 2657 | }, 2658 | { 2659 | "geometry": { 2660 | "coordinates": [ 2661 | 116.41631722898292, 2662 | 39.869180770516394 2663 | ], 2664 | "type": "Point" 2665 | }, 2666 | "properties": { 2667 | "name": "117.79.148.43", 2668 | "place": "Beijing, China", 2669 | "show_on_map": true 2670 | }, 2671 | "type": "Feature" 2672 | }, 2673 | { 2674 | "geometry": { 2675 | "coordinates": [ 2676 | -117.8535, 2677 | 34.0115 2678 | ], 2679 | "type": "Point" 2680 | }, 2681 | "properties": { 2682 | "name": "74.117.60.227", 2683 | "place": "Walnut, United States", 2684 | "show_on_map": true 2685 | }, 2686 | "type": "Feature" 2687 | }, 2688 | { 2689 | "geometry": { 2690 | "coordinates": [ 2691 | 121.47438698283877, 2692 | 31.110136532830662 2693 | ], 2694 | "type": "Point" 2695 | }, 2696 | "properties": { 2697 | "name": "114.80.224.90", 2698 | "place": "Shanghai, China", 2699 | "show_on_map": true 2700 | }, 2701 | "type": "Feature" 2702 | }, 2703 | { 2704 | "geometry": { 2705 | "coordinates": [ 2706 | 118.4983, 2707 | 24.9878 2708 | ], 2709 | "type": "Point" 2710 | }, 2711 | "properties": { 2712 | "name": "220.161.148.178", 2713 | "place": "Putian, China", 2714 | "show_on_map": true 2715 | }, 2716 | "type": "Feature" 2717 | }, 2718 | { 2719 | "geometry": { 2720 | "coordinates": [ 2721 | 116.47279092993246, 2722 | 39.861426885822276 2723 | ], 2724 | "type": "Point" 2725 | }, 2726 | "properties": { 2727 | "name": "223.4.203.2", 2728 | "place": "Beijing, China", 2729 | "show_on_map": true 2730 | }, 2731 | "type": "Feature" 2732 | }, 2733 | { 2734 | "geometry": { 2735 | "coordinates": [ 2736 | 115.9333, 2737 | 28.55 2738 | ], 2739 | "type": "Point" 2740 | }, 2741 | "properties": { 2742 | "name": "113.195.5.231", 2743 | "place": "Nanchang, China", 2744 | "show_on_map": true 2745 | }, 2746 | "type": "Feature" 2747 | }, 2748 | { 2749 | "geometry": { 2750 | "coordinates": [ 2751 | -0.13, 2752 | 51.5 2753 | ], 2754 | "type": "Point" 2755 | }, 2756 | "properties": { 2757 | "name": "109.169.77.208", 2758 | "place": ", United Kingdom", 2759 | "show_on_map": true 2760 | }, 2761 | "type": "Feature" 2762 | }, 2763 | { 2764 | "geometry": { 2765 | "coordinates": [ 2766 | 121.48715021194113, 2767 | 31.061374497073512 2768 | ], 2769 | "type": "Point" 2770 | }, 2771 | "properties": { 2772 | "name": "211.152.52.50", 2773 | "place": "Shanghai, China", 2774 | "show_on_map": true 2775 | }, 2776 | "type": "Feature" 2777 | } 2778 | ], 2779 | "type": "FeatureCollection" 2780 | } -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin:0; 3 | padding:0; 4 | height:100%; 5 | } 6 | #map{ 7 | margin:0; 8 | padding:0; 9 | width:100%; 10 | height:100%; 11 | } 12 | .leaflet-label { 13 | background: rgb(235, 235, 235); 14 | background: rgba(235, 235, 235, 0.81); 15 | background-clip: padding-box; 16 | border-color: #777; 17 | border-color: rgba(0,0,0,0.25); 18 | border-radius: 4px; 19 | border-style: solid; 20 | border-width: 4px; 21 | color: #111; 22 | display: block; 23 | font: 12px/20px "Helvetica Neue", Arial, Helvetica, sans-serif; 24 | font-weight: bold; 25 | padding: 1px 6px; 26 | position: absolute; 27 | -webkit-user-select: none; 28 | -moz-user-select: none; 29 | -ms-user-select: none; 30 | user-select: none; 31 | white-space: nowrap; 32 | z-index: 6; 33 | } 34 | 35 | .leaflet-label:before { 36 | border-right: 6px solid black; 37 | border-right-color: inherit; 38 | border-top: 6px solid transparent; 39 | border-bottom: 6px solid transparent; 40 | content: ""; 41 | position: absolute; 42 | top: 5px; 43 | left: -10px; 44 | } 45 | --------------------------------------------------------------------------------