update ipv4 map example, including bug fixes

This commit is contained in:
rolux 2012-07-03 18:19:55 +02:00
parent f3334c13cf
commit a150457748
2 changed files with 83 additions and 57 deletions

View file

@ -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';

View file

@ -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