update ipv4 map example, including bug fixes
This commit is contained in:
parent
f3334c13cf
commit
a150457748
2 changed files with 83 additions and 57 deletions
|
@ -1,3 +1,14 @@
|
||||||
|
/*
|
||||||
|
The code below implements a map widget to navigate a zoomable globe made of
|
||||||
|
tiled images — similar to Google Maps. The catch is that the map doesn't
|
||||||
|
show the surface of the Earth, but the Internet instead of geographical
|
||||||
|
or political boundaries, it maps the country-wise allocation of the IPv4 address
|
||||||
|
space.
|
||||||
|
|
||||||
|
Inline comments, below, are minimal — but the widget has its own "About"
|
||||||
|
section that explains how it was done.
|
||||||
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -57,12 +68,13 @@ Ox.load('UI', function() {
|
||||||
key_m: function() { self.$toggle.trigger('click'); },
|
key_m: function() { self.$toggle.trigger('click'); },
|
||||||
key_minus: function() { zoomBy(-1); },
|
key_minus: function() { zoomBy(-1); },
|
||||||
key_right: function() { panBy([0.5, 0]); },
|
key_right: function() { panBy([0.5, 0]); },
|
||||||
key_shift_down: function() { panBy([0, 1]) },
|
key_shift_down: function() { panBy([0, 1]); },
|
||||||
|
key_shift_enter: function() { find(''); },
|
||||||
key_shift_equal: function() { zoomBy(2); },
|
key_shift_equal: function() { zoomBy(2); },
|
||||||
key_shift_left: function() { panBy([-1, 0]) },
|
key_shift_left: function() { panBy([-1, 0]); },
|
||||||
key_shift_minus: function() { zoomBy(-2); },
|
key_shift_minus: function() { zoomBy(-2); },
|
||||||
key_shift_right: function() { panBy([1, 0]) },
|
key_shift_right: function() { panBy([1, 0]); },
|
||||||
key_shift_up: function() { panBy([0, -1]) },
|
key_shift_up: function() { panBy([0, -1]); },
|
||||||
key_slash: function() { toggleOverlay('milliondollarhomepage'); },
|
key_slash: function() { toggleOverlay('milliondollarhomepage'); },
|
||||||
key_up: function() { panBy([0, -0.5]); },
|
key_up: function() { panBy([0, -0.5]); },
|
||||||
mousedown: function(e) {
|
mousedown: function(e) {
|
||||||
|
@ -102,7 +114,8 @@ Ox.load('UI', function() {
|
||||||
self.mapSize = Math.pow(2, self.options.zoom) * self.tileSize;
|
self.mapSize = Math.pow(2, self.options.zoom) * self.tileSize;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Tiles layer.
|
Tiles layer with a dynamic tooltip that reacts to mousewheel, drag and
|
||||||
|
click events.
|
||||||
*/
|
*/
|
||||||
self.$tiles = Ox.Element({
|
self.$tiles = Ox.Element({
|
||||||
tooltip: function(e) {
|
tooltip: function(e) {
|
||||||
|
@ -131,7 +144,7 @@ Ox.load('UI', function() {
|
||||||
.appendTo(that);
|
.appendTo(that);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
About button.
|
About button that opens a dialog.
|
||||||
*/
|
*/
|
||||||
self.$about = Ox.Button({
|
self.$about = Ox.Button({
|
||||||
selectable: false,
|
selectable: false,
|
||||||
|
@ -156,7 +169,7 @@ Ox.load('UI', function() {
|
||||||
.appendTo(that);
|
.appendTo(that);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The panel inside the about dialog.
|
The tabbed panel inside the about dialog.
|
||||||
*/
|
*/
|
||||||
self.$panel = Ox.TabPanel({
|
self.$panel = Ox.TabPanel({
|
||||||
content: {
|
content: {
|
||||||
|
@ -187,6 +200,25 @@ Ox.load('UI', function() {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
The images in the "About" section.
|
||||||
|
*/
|
||||||
|
[
|
||||||
|
'png/xkcd.png', 'png/projection.png', 'png/flags.png', 'png/map.png'
|
||||||
|
].forEach(function(image) {
|
||||||
|
$('<a>')
|
||||||
|
.attr({href: image, target: '_blank'})
|
||||||
|
.append(
|
||||||
|
$('<img>')
|
||||||
|
.attr({src: image})
|
||||||
|
.css({width: '256px', marginBottom: '16px'})
|
||||||
|
)
|
||||||
|
.appendTo(self.$images);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
The contents of the "Mouse & Keyboard Controls" panel.
|
||||||
|
*/
|
||||||
Ox.forEach({
|
Ox.forEach({
|
||||||
'Mouse': {
|
'Mouse': {
|
||||||
'Click': 'Pan to position / Select marker',
|
'Click': 'Pan to position / Select marker',
|
||||||
|
@ -238,7 +270,7 @@ Ox.load('UI', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
About dialog.
|
The dialog itself.
|
||||||
*/
|
*/
|
||||||
self.$dialog = Ox.Dialog({
|
self.$dialog = Ox.Dialog({
|
||||||
buttons: [
|
buttons: [
|
||||||
|
@ -261,21 +293,9 @@ Ox.load('UI', function() {
|
||||||
width: 560 + Ox.UI.SCROLLBAR_SIZE
|
width: 560 + Ox.UI.SCROLLBAR_SIZE
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
|
||||||
'png/xkcd.png', 'png/projection.png', 'png/flags.png', 'png/map.png'
|
|
||||||
].forEach(function(image) {
|
|
||||||
$('<a>')
|
|
||||||
.attr({href: image, target: '_blank'})
|
|
||||||
.append(
|
|
||||||
$('<img>')
|
|
||||||
.attr({src: image})
|
|
||||||
.css({width: '256px', marginBottom: '16px'})
|
|
||||||
)
|
|
||||||
.appendTo(self.$images);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Find element.
|
Find element to search for host names or IP addresses, with a menu to
|
||||||
|
select pre-defined bookmarks.
|
||||||
*/
|
*/
|
||||||
self.$findElement = Ox.FormElementGroup({
|
self.$findElement = Ox.FormElementGroup({
|
||||||
elements: [
|
elements: [
|
||||||
|
@ -318,7 +338,7 @@ Ox.load('UI', function() {
|
||||||
.appendTo(that);
|
.appendTo(that);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Zoom control.
|
Zoom control. 0 is the lowest zoom level, 8 is the highest.
|
||||||
*/
|
*/
|
||||||
self.$zoom = Ox.Range({
|
self.$zoom = Ox.Range({
|
||||||
arrows: true,
|
arrows: true,
|
||||||
|
@ -339,7 +359,7 @@ Ox.load('UI', function() {
|
||||||
.appendTo(that);
|
.appendTo(that);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Overview map control.
|
Button that toggles the overview map.
|
||||||
*/
|
*/
|
||||||
self.$toggle = Ox.Button({
|
self.$toggle = Ox.Button({
|
||||||
title: 'Show Overview Map',
|
title: 'Show Overview Map',
|
||||||
|
@ -353,7 +373,7 @@ Ox.load('UI', function() {
|
||||||
.appendTo(that);
|
.appendTo(that);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Overview map.
|
The overview map itself, showing the entire internet.
|
||||||
*/
|
*/
|
||||||
self.$world = Ox.Element({
|
self.$world = Ox.Element({
|
||||||
tooltip: function(e) {
|
tooltip: function(e) {
|
||||||
|
@ -381,7 +401,7 @@ Ox.load('UI', function() {
|
||||||
.appendTo(that);
|
.appendTo(that);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Position marker on overview map.
|
The position marker on overview map.
|
||||||
*/
|
*/
|
||||||
self.$point = Ox.Element()
|
self.$point = Ox.Element()
|
||||||
.addClass('marker')
|
.addClass('marker')
|
||||||
|
@ -394,6 +414,9 @@ Ox.load('UI', function() {
|
||||||
.attr({id: 'regions'})
|
.attr({id: 'regions'})
|
||||||
.appendTo(self.$world);
|
.appendTo(self.$world);
|
||||||
|
|
||||||
|
/*
|
||||||
|
The regions of the overview map, 'center' being the visible area.
|
||||||
|
*/
|
||||||
[
|
[
|
||||||
'center', 'left', 'right', 'top', 'bottom'
|
'center', 'left', 'right', 'top', 'bottom'
|
||||||
].forEach(function(region) {
|
].forEach(function(region) {
|
||||||
|
@ -415,7 +438,8 @@ Ox.load('UI', function() {
|
||||||
document.location.hash && onHashchange();
|
document.location.hash && onHashchange();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Looks up a given host name or IP address.
|
Looks up a given host name or IP address and then, if there is a result,
|
||||||
|
pans to its position and adds a marker.
|
||||||
*/
|
*/
|
||||||
function find(value) {
|
function find(value) {
|
||||||
var isHost, query = '';
|
var isHost, query = '';
|
||||||
|
@ -434,7 +458,6 @@ Ox.load('UI', function() {
|
||||||
self.$find.options({value: value});
|
self.$find.options({value: value});
|
||||||
getJSONP(self.options.lookupURL + query, function(data) {
|
getJSONP(self.options.lookupURL + query, function(data) {
|
||||||
if (isHost && !isIP(data.ip)) {
|
if (isHost && !isIP(data.ip)) {
|
||||||
//document.location.hash = '';
|
|
||||||
self.$find.addClass('OxError');
|
self.$find.addClass('OxError');
|
||||||
} else {
|
} else {
|
||||||
data.host = value ? data.host : 'Me';
|
data.host = value ? data.host : 'Me';
|
||||||
|
@ -456,7 +479,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Returns an IP address for a given number.
|
Translates an given integer into an IP address.
|
||||||
*/
|
*/
|
||||||
function getIPByN(n) {
|
function getIPByN(n) {
|
||||||
return Ox.range(4).map(function(i) {
|
return Ox.range(4).map(function(i) {
|
||||||
|
@ -471,14 +494,12 @@ Ox.load('UI', function() {
|
||||||
var n = 0, path = self.path,
|
var n = 0, path = self.path,
|
||||||
z = zoom === void 0 ? self.options.zoom : zoom;
|
z = zoom === void 0 ? self.options.zoom : zoom;
|
||||||
Ox.loop(8 + z, function(i) {
|
Ox.loop(8 + z, function(i) {
|
||||||
try {
|
|
||||||
var p2 = Math.pow(2, 7 + z - i),
|
var p2 = Math.pow(2, 7 + z - i),
|
||||||
p4 = Math.pow(4, 7 + z - i),
|
p4 = Math.pow(4, 7 + z - i),
|
||||||
xy_ = xy.map(function(v) {
|
xy_ = xy.map(function(v) {
|
||||||
return Math.floor(v / p2);
|
return Math.floor(v / p2);
|
||||||
}),
|
}),
|
||||||
q = self.projection[path][0].indexOf(xy_[0] + xy_[1] * 2);
|
q = self.projection[path][0].indexOf(xy_[0] + xy_[1] * 2);
|
||||||
} catch(e) { Ox.print('ERROR', q, path); }
|
|
||||||
n += q * p4;
|
n += q * p4;
|
||||||
xy = xy.map(function(v, i) {
|
xy = xy.map(function(v, i) {
|
||||||
return v - xy_[i] * p2;
|
return v - xy_[i] * p2;
|
||||||
|
@ -494,7 +515,7 @@ Ox.load('UI', function() {
|
||||||
var getJSONP = Ox.cache(Ox.getJSONP, {async: true});
|
var getJSONP = Ox.cache(Ox.getJSONP, {async: true});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Returns a marker for a given mouse event.
|
Returns the marker that recieved a given click event, or null.
|
||||||
*/
|
*/
|
||||||
function getMarker(e) {
|
function getMarker(e) {
|
||||||
var $target = $(e.target);
|
var $target = $(e.target);
|
||||||
|
@ -504,7 +525,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Returns a number for a given IP address.
|
Translates a given IP adress into an integer.
|
||||||
*/
|
*/
|
||||||
function getNByIP(ip) {
|
function getNByIP(ip) {
|
||||||
return ip.split('.').reduce(function(prev, curr, i) {
|
return ip.split('.').reduce(function(prev, curr, i) {
|
||||||
|
@ -583,14 +604,14 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles doubleclick event.
|
Handles doubleclick events by delegating to the mousewheel handler.
|
||||||
*/
|
*/
|
||||||
function onDoubleclick(e) {
|
function onDoubleclick(e) {
|
||||||
onMousewheel(e, 0, 0, e.shiftKey ? - 1 : 1);
|
onMousewheel(e, 0, 0, e.shiftKey ? - 1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles drag event for map and overview map.
|
Handles drag events for both the main map and the overview map.
|
||||||
*/
|
*/
|
||||||
function onDrag(e) {
|
function onDrag(e) {
|
||||||
var delta = [e.clientDX, e.clientDY];
|
var delta = [e.clientDX, e.clientDY];
|
||||||
|
@ -601,7 +622,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles dragstart event for map and overview map.
|
Handles dragstart events for both the main map and the overview map.
|
||||||
*/
|
*/
|
||||||
function onDragstart(e, isWorld) {
|
function onDragstart(e, isWorld) {
|
||||||
self.dragstartXY = self.xy;
|
self.dragstartXY = self.xy;
|
||||||
|
@ -609,7 +630,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles hashchange event.
|
Handles hashchange events.
|
||||||
*/
|
*/
|
||||||
function onHashchange() {
|
function onHashchange() {
|
||||||
var parts;
|
var parts;
|
||||||
|
@ -624,7 +645,8 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles mousewheel event.
|
Handles mousewheel events (zooms in or out, but maintains the position
|
||||||
|
that the mouse is pointing at).
|
||||||
*/
|
*/
|
||||||
function onMousewheel(e, delta, deltaX, deltaY) {
|
function onMousewheel(e, delta, deltaX, deltaY) {
|
||||||
var $marker = getMarker(e),
|
var $marker = getMarker(e),
|
||||||
|
@ -641,10 +663,13 @@ Ox.load('UI', function() {
|
||||||
setIP($marker.attr('id'));
|
setIP($marker.attr('id'));
|
||||||
} else {
|
} else {
|
||||||
setXY(self.xy.map(function(xy, i) {
|
setXY(self.xy.map(function(xy, i) {
|
||||||
return deltaZ == -1
|
return Ox.limit(
|
||||||
? 2 * xy - mouseXY[i]
|
deltaZ == -1
|
||||||
: (xy + mouseXY[i]) / 2
|
? 2 * xy - mouseXY[i]
|
||||||
}));
|
: (xy + mouseXY[i]) / 2,
|
||||||
|
0, self.mapSize - 1
|
||||||
|
);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
zoomBy(deltaZ);
|
zoomBy(deltaZ);
|
||||||
self.zooming = true;
|
self.zooming = true;
|
||||||
|
@ -656,7 +681,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles window resize event.
|
Handles window resize events.
|
||||||
*/
|
*/
|
||||||
function onResize() {
|
function onResize() {
|
||||||
self.mapCenter = [
|
self.mapCenter = [
|
||||||
|
@ -680,11 +705,13 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handles singleclick event.
|
Handles singleclick events. Clicking on a marker selects it, but holding
|
||||||
|
the Meta key toggles its selected state instead, and holding shift adds
|
||||||
|
it to the current selection. Clicking on the map and holding shift adds
|
||||||
|
a marker at that place.
|
||||||
*/
|
*/
|
||||||
function onSingleclick(e) {
|
function onSingleclick(e) {
|
||||||
var $marker = getMarker(e),
|
var $marker = getMarker(e), ip;
|
||||||
ip = getIPByMouseXY(e);
|
|
||||||
if ($marker) {
|
if ($marker) {
|
||||||
if (e.metaKey) {
|
if (e.metaKey) {
|
||||||
$marker.toggleClass('selected');
|
$marker.toggleClass('selected');
|
||||||
|
@ -694,6 +721,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$('.marker.selected').removeClass('selected');
|
$('.marker.selected').removeClass('selected');
|
||||||
|
ip = getIPByMouseXY(e);
|
||||||
panTo(getXYByMouseXY(e), function() {
|
panTo(getXYByMouseXY(e), function() {
|
||||||
e.shiftKey && find(ip);
|
e.shiftKey && find(ip);
|
||||||
});
|
});
|
||||||
|
@ -731,7 +759,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Renders the map.
|
Renders the tiles of the map.
|
||||||
*/
|
*/
|
||||||
function renderMap() {
|
function renderMap() {
|
||||||
var halfWidth, halfHeight;
|
var halfWidth, halfHeight;
|
||||||
|
@ -766,11 +794,10 @@ Ox.load('UI', function() {
|
||||||
});
|
});
|
||||||
updateWorld();
|
updateWorld();
|
||||||
$('.OxTooltip').remove();
|
$('.OxTooltip').remove();
|
||||||
//document.location.hash = self.options.ip + ',' + self.options.zoom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Renders a marker.
|
Renders a map marker with the given properties.
|
||||||
*/
|
*/
|
||||||
function renderMarker(data) {
|
function renderMarker(data) {
|
||||||
var $icon, $marker = self.$tiles.find('div[id="' + data.ip + '"]'),
|
var $icon, $marker = self.$tiles.find('div[id="' + data.ip + '"]'),
|
||||||
|
@ -875,7 +902,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Sets IP and XY.
|
Updates XY when setting the IP address.
|
||||||
*/
|
*/
|
||||||
function setIP(ip) {
|
function setIP(ip) {
|
||||||
self.options.ip = ip;
|
self.options.ip = ip;
|
||||||
|
@ -883,7 +910,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Sets XY and IP.
|
Updates the IP address when setting XY.
|
||||||
*/
|
*/
|
||||||
function setXY(xy) {
|
function setXY(xy) {
|
||||||
self.xy = xy;
|
self.xy = xy;
|
||||||
|
@ -891,7 +918,7 @@ Ox.load('UI', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Toggles overlay image.
|
Toggles the overlay image.
|
||||||
*/
|
*/
|
||||||
function toggleOverlay(image) {
|
function toggleOverlay(image) {
|
||||||
var $overlay = $('#overlay'), src = 'png/' + image + '.png';
|
var $overlay = $('#overlay'), src = 'png/' + image + '.png';
|
||||||
|
|
|
@ -8,8 +8,6 @@ import os
|
||||||
import ox
|
import ox
|
||||||
import pygeoip
|
import pygeoip
|
||||||
|
|
||||||
# fixme: pow(a, b) -> a ** b
|
|
||||||
|
|
||||||
geoip = pygeoip.GeoIP('../dat/GeoLiteCity.dat', pygeoip.MEMORY_CACHE)
|
geoip = pygeoip.GeoIP('../dat/GeoLiteCity.dat', pygeoip.MEMORY_CACHE)
|
||||||
path = '../../../../oxjs.org/source/Ox.Geo/png/flags/'
|
path = '../../../../oxjs.org/source/Ox.Geo/png/flags/'
|
||||||
projection = {
|
projection = {
|
||||||
|
@ -68,7 +66,7 @@ def render_flags():
|
||||||
# renders an image of 256 flags
|
# renders an image of 256 flags
|
||||||
image = Image.new('RGB', (1024, 1024))
|
image = Image.new('RGB', (1024, 1024))
|
||||||
font = '../ttf/DejaVuSansMonoBold.ttf'
|
font = '../ttf/DejaVuSansMonoBold.ttf'
|
||||||
countries = ox.file.read_json('../../../../oxjs.org/oxjs/source/Ox.Geo/json/Ox.Geo.json')
|
countries = ox.file.read_json(path.replace('png/flags/', 'json/Ox.Geo.json'))
|
||||||
countries = filter(lambda x: len(x['code']) == 2 and not x['code'] in ['UK', 'XK'], countries)
|
countries = filter(lambda x: len(x['code']) == 2 and not x['code'] in ['UK', 'XK'], countries)
|
||||||
for i, country in enumerate(sorted(countries, key=lambda x: x['code'])):
|
for i, country in enumerate(sorted(countries, key=lambda x: x['code'])):
|
||||||
flag = get_flag_image(country['code'], 64)
|
flag = get_flag_image(country['code'], 64)
|
||||||
|
@ -180,12 +178,13 @@ def render_projection():
|
||||||
image.save('../png/projection.png')
|
image.save('../png/projection.png')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# qlmanage -t -s 16384 -o . ../../../../oxjs.org/tools/geo/svg/icons/NTHH.svg
|
|
||||||
render_flags()
|
render_flags()
|
||||||
render_projection()
|
render_projection()
|
||||||
render_tiles()
|
render_tiles()
|
||||||
render_tile('0.0.0.0', 0)
|
render_tile('0.0.0.0', 0)
|
||||||
render_map()
|
render_map()
|
||||||
|
|
||||||
|
# to get a sufficiently large "international" icon, run:
|
||||||
|
# qlmanage -t -s 16384 -o . ../../../../oxjs.org/tools/geo/svg/icons/NTHH.svg
|
||||||
|
|
||||||
|
|
||||||
# http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
|
|
||||||
|
|
Loading…
Reference in a new issue