From a15045774870a4166190e2afbc87f272a879a44a Mon Sep 17 00:00:00 2001 From: rolux Date: Tue, 3 Jul 2012 18:19:55 +0200 Subject: [PATCH] update ipv4 map example, including bug fixes --- .../ipv4_map_of_the_internet/js/example.js | 131 +++++++++++------- .../txt/ipv4map.py.txt | 9 +- 2 files changed, 83 insertions(+), 57 deletions(-) diff --git a/examples/maps/ipv4_map_of_the_internet/js/example.js b/examples/maps/ipv4_map_of_the_internet/js/example.js index a93b5ad5..b82b52be 100644 --- a/examples/maps/ipv4_map_of_the_internet/js/example.js +++ b/examples/maps/ipv4_map_of_the_internet/js/example.js @@ -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'; /* @@ -57,12 +68,13 @@ Ox.load('UI', function() { key_m: function() { self.$toggle.trigger('click'); }, key_minus: function() { zoomBy(-1); }, 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_left: function() { panBy([-1, 0]) }, + key_shift_left: function() { panBy([-1, 0]); }, key_shift_minus: function() { zoomBy(-2); }, - key_shift_right: function() { panBy([1, 0]) }, - key_shift_up: function() { panBy([0, -1]) }, + key_shift_right: function() { panBy([1, 0]); }, + key_shift_up: function() { panBy([0, -1]); }, key_slash: function() { toggleOverlay('milliondollarhomepage'); }, key_up: function() { panBy([0, -0.5]); }, mousedown: function(e) { @@ -102,7 +114,8 @@ Ox.load('UI', function() { 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({ tooltip: function(e) { @@ -131,7 +144,7 @@ Ox.load('UI', function() { .appendTo(that); /* - About button. + About button that opens a dialog. */ self.$about = Ox.Button({ selectable: false, @@ -156,7 +169,7 @@ Ox.load('UI', function() { .appendTo(that); /* - The panel inside the about dialog. + The tabbed panel inside the about dialog. */ self.$panel = Ox.TabPanel({ 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) { + $('') + .attr({href: image, target: '_blank'}) + .append( + $('') + .attr({src: image}) + .css({width: '256px', marginBottom: '16px'}) + ) + .appendTo(self.$images); + }); + + /* + The contents of the "Mouse & Keyboard Controls" panel. + */ Ox.forEach({ 'Mouse': { 'Click': 'Pan to position / Select marker', @@ -238,7 +270,7 @@ Ox.load('UI', function() { }); /* - About dialog. + The dialog itself. */ self.$dialog = Ox.Dialog({ buttons: [ @@ -261,21 +293,9 @@ Ox.load('UI', function() { width: 560 + Ox.UI.SCROLLBAR_SIZE }); - [ - 'png/xkcd.png', 'png/projection.png', 'png/flags.png', 'png/map.png' - ].forEach(function(image) { - $('') - .attr({href: image, target: '_blank'}) - .append( - $('') - .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({ elements: [ @@ -318,7 +338,7 @@ Ox.load('UI', function() { .appendTo(that); /* - Zoom control. + Zoom control. 0 is the lowest zoom level, 8 is the highest. */ self.$zoom = Ox.Range({ arrows: true, @@ -339,7 +359,7 @@ Ox.load('UI', function() { .appendTo(that); /* - Overview map control. + Button that toggles the overview map. */ self.$toggle = Ox.Button({ title: 'Show Overview Map', @@ -353,7 +373,7 @@ Ox.load('UI', function() { .appendTo(that); /* - Overview map. + The overview map itself, showing the entire internet. */ self.$world = Ox.Element({ tooltip: function(e) { @@ -381,7 +401,7 @@ Ox.load('UI', function() { .appendTo(that); /* - Position marker on overview map. + The position marker on overview map. */ self.$point = Ox.Element() .addClass('marker') @@ -394,6 +414,9 @@ Ox.load('UI', function() { .attr({id: 'regions'}) .appendTo(self.$world); + /* + The regions of the overview map, 'center' being the visible area. + */ [ 'center', 'left', 'right', 'top', 'bottom' ].forEach(function(region) { @@ -415,7 +438,8 @@ Ox.load('UI', function() { 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) { var isHost, query = ''; @@ -434,7 +458,6 @@ Ox.load('UI', function() { self.$find.options({value: value}); getJSONP(self.options.lookupURL + query, function(data) { if (isHost && !isIP(data.ip)) { - //document.location.hash = ''; self.$find.addClass('OxError'); } else { 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) { return Ox.range(4).map(function(i) { @@ -471,14 +494,12 @@ Ox.load('UI', function() { var n = 0, path = self.path, z = zoom === void 0 ? self.options.zoom : zoom; Ox.loop(8 + z, function(i) { - try { var p2 = Math.pow(2, 7 + z - i), p4 = Math.pow(4, 7 + z - i), xy_ = xy.map(function(v) { return Math.floor(v / p2); }), q = self.projection[path][0].indexOf(xy_[0] + xy_[1] * 2); - } catch(e) { Ox.print('ERROR', q, path); } n += q * p4; xy = xy.map(function(v, i) { return v - xy_[i] * p2; @@ -494,7 +515,7 @@ Ox.load('UI', function() { 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) { 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) { 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) { 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) { 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) { self.dragstartXY = self.xy; @@ -609,7 +630,7 @@ Ox.load('UI', function() { } /* - Handles hashchange event. + Handles hashchange events. */ function onHashchange() { 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) { var $marker = getMarker(e), @@ -641,10 +663,13 @@ Ox.load('UI', function() { setIP($marker.attr('id')); } else { setXY(self.xy.map(function(xy, i) { - return deltaZ == -1 - ? 2 * xy - mouseXY[i] - : (xy + mouseXY[i]) / 2 - })); + return Ox.limit( + deltaZ == -1 + ? 2 * xy - mouseXY[i] + : (xy + mouseXY[i]) / 2, + 0, self.mapSize - 1 + ); + })); } zoomBy(deltaZ); self.zooming = true; @@ -656,7 +681,7 @@ Ox.load('UI', function() { } /* - Handles window resize event. + Handles window resize events. */ function onResize() { 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) { - var $marker = getMarker(e), - ip = getIPByMouseXY(e); + var $marker = getMarker(e), ip; if ($marker) { if (e.metaKey) { $marker.toggleClass('selected'); @@ -694,6 +721,7 @@ Ox.load('UI', function() { } } else { $('.marker.selected').removeClass('selected'); + ip = getIPByMouseXY(e); panTo(getXYByMouseXY(e), function() { e.shiftKey && find(ip); }); @@ -731,7 +759,7 @@ Ox.load('UI', function() { } /* - Renders the map. + Renders the tiles of the map. */ function renderMap() { var halfWidth, halfHeight; @@ -766,11 +794,10 @@ Ox.load('UI', function() { }); updateWorld(); $('.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) { 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) { 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) { self.xy = xy; @@ -891,7 +918,7 @@ Ox.load('UI', function() { } /* - Toggles overlay image. + Toggles the overlay image. */ function toggleOverlay(image) { var $overlay = $('#overlay'), src = 'png/' + image + '.png'; diff --git a/examples/maps/ipv4_map_of_the_internet/txt/ipv4map.py.txt b/examples/maps/ipv4_map_of_the_internet/txt/ipv4map.py.txt index 193df43a..828a9b5e 100644 --- a/examples/maps/ipv4_map_of_the_internet/txt/ipv4map.py.txt +++ b/examples/maps/ipv4_map_of_the_internet/txt/ipv4map.py.txt @@ -8,8 +8,6 @@ import os import ox import pygeoip -# fixme: pow(a, b) -> a ** b - geoip = pygeoip.GeoIP('../dat/GeoLiteCity.dat', pygeoip.MEMORY_CACHE) path = '../../../../oxjs.org/source/Ox.Geo/png/flags/' projection = { @@ -68,7 +66,7 @@ def render_flags(): # renders an image of 256 flags image = Image.new('RGB', (1024, 1024)) 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) for i, country in enumerate(sorted(countries, key=lambda x: x['code'])): flag = get_flag_image(country['code'], 64) @@ -180,12 +178,13 @@ def render_projection(): image.save('../png/projection.png') if __name__ == '__main__': - # qlmanage -t -s 16384 -o . ../../../../oxjs.org/tools/geo/svg/icons/NTHH.svg render_flags() render_projection() render_tiles() render_tile('0.0.0.0', 0) 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