diff --git a/examples/maps/manhattan_grid/js/example.js b/examples/maps/manhattan_grid/js/example.js index edd8080b..9456b28f 100644 --- a/examples/maps/manhattan_grid/js/example.js +++ b/examples/maps/manhattan_grid/js/example.js @@ -8,10 +8,10 @@ Avenue A) and Zero St (a.k.a. Houston St). Avenues east of Zero Ave, just as Streets south of Zero St, have negative numbers. Broadway, which will split not only Manhattan but the entire globe into an eastern and a western hemisphere, retains its orientation, but is adjusted slightly so that it originates at the -intersection of Zero & Zero. From there, Broadway, Zero Avenue and Zero Street -continue as perfectly straight equatorial lines. All three will intersect once -more, exactly halfway, in the Indian Ocean, (southwest of Australia), at the -point furthest from Manhattan. +intersection of Zero & Zero. From there, Broadway, Zero Ave and Zero St continue +as perfectly straight equatorial lines. All three will intersect once more, +exactly halfway, in the Indian Ocean, (southwest of Australia), at the point +furthest from Manhattan. As subsequent avenues remain exactly parallel to Zero Ave, and subsequent streets exactly parallel to Zero St, they form smaller and smaller circles @@ -121,8 +121,8 @@ Ox.load('Image', function() { bearings.streets ); /* - The second intersection of Zero Avenue, Zero Street and Broadway is half of - the Earth's circumference away from the first one, in any direction. + The second intersection of Zero Ave, Zero St and Broadway is half of the + Earth's circumference away from the first one, in any direction. */ points['-0 & -0'] = Ox.getPoint( points['0 & 0'], @@ -279,7 +279,7 @@ Ox.load('Image', function() { function drawPath(image, path, options) { var n, parts = [[]]; /* - ... + Close the path by appending the first point. */ path.push(path[0]); n = path.length; @@ -316,14 +316,23 @@ Ox.load('Image', function() { } /* - ... + Now it's time to load our map image. */ Ox.Image('jpg/earth1024.jpg', function(image) { mapSize = image.getSize().width; + /* + First, we draw a circle, centered at the intersection of Zero Ave and + Zero St, with a radius of the quarter of the Earth's circumference. This + is the line that runs through all four poles of our coordinate system. + */ drawPath(image, Ox.getCircle(points['0 & 0'], C / 4, 8), { color: 'rgba(255, 255, 255, 0.25)' }); + /* + Then, we draw the streets, avenues and Broadway. Zero St, Zero Ave and + Broadway will be twice as bold as the others. + */ ['streets', 'avenues', 'broadway'].forEach(function(type) { lines[type].forEach(function(line, i) { drawPath(image, line, { @@ -333,30 +342,51 @@ Ox.load('Image', function() { }); }); + /* + Now we load the map image as the background of our document, and attach + a few event handlers. + */ $body.css({ minWidth: mapSize + 'px', height: mapSize + 'px', backgroundImage: 'url(' + image.src() + ')' }) .on({ - click: click, - mouseover: mouseover, - mousemove: mousemove, - mouseout: mouseout + click: onClick, + mouseover: onMouseover, + mousemove: onMousemove, + mouseout: onMouseout }); + /* + As an extra feature, we want to provide more detailed renderings at a + higher zoom level, for three points of interest. (Our point in Paris is + the closest Manhattan Grid intersection to the Étoile, a real-world + intersection of twelve large avenues.) + */ + points['Paris'] = {lat: 48.87377, lng: 2.29505}; [ {point: points['0 & 0'], title: 'Manhattan', z: 12}, - {point: {lat: 48.87377, lng: 2.29505}, title: 'Paris', z: 13}, + {point: getIntersection(points['Paris']), title: 'Paris', z: 13}, {point: poles.north, title: 'Uzbekistan', z: 14} ].forEach(function(marker, i) { + /* + We're trying to make this function as generic as possible: for any + given point and zoom level, it would allow us the retrieve the + corresponding Google Maps tile. Even though we are just using three + local images here, their naming scheme matches the logic of Google + Maps. Manhattan, for example, is "jpg/v=108&x=1206&y=1539&z=12.jpg". + */ var as = getASByLatLng(marker.point), g = {s: 256, v: 108, z: marker.z}, xy = getXYByLatLng(marker.point); - Ox.print(as) Ox.extend(g, Ox.map(Ox.getXYByLatLng(marker.point), function(v) { return Math.floor(v * Math.pow(2, g.z)); })); + /* + For each point, we add a marker, with a click handler that will + display the corresponding image. + */ Ox.$('
') .addClass('marker') .css({ @@ -372,32 +402,58 @@ Ox.load('Image', function() { } }) .appendTo($body); + /* + Now we load the image. + */ Ox.Image(Ox.formatString( 'jpg/v={v}&x={x}&y={y}&z={z}.jpg', g ), function(image) { + /* + First, we draw the streets. + */ if (marker.title == 'Uzbekistan') { - Ox.range( + /* + Uzbekistan, the North Pole of our projection, is a special + case, as the streets run in circles around it. (The exact + number of streets is a float — 123582.49214895045 + — so the radius of the northernmost street — + 123,582th St — is 0.492 times the distance between + streets.) + */ + Ox.loop( distances.streets * (numbers.streets % 1), 2000, - distances.streets - ).forEach(function(distance) { - var circle = mapLine(Ox.getCircle( - poles.north, distance, precision - ), g); - image.drawPath(circle, { - close: true, - color: colors.streets - }); - }); + distances.streets, + function(distance) { + var circle = mapLine(Ox.getCircle( + poles.north, distance, precision + ), g); + image.drawPath(circle, { + close: true, + color: colors.streets + }); + } + ); } else { + /* + Otherwise, we draw all streets from 200 streets "south" to + 200 steets "north" of the point, using `getLine`, a helper + function defined below. + */ Ox.loop(-200, 200, function(street) { - var line = getLine(g, marker.point, as, 'streets', street); + var line = getLine( + g, marker.point, as, 'streets', street + ); image.drawPath(line, { color: colors.streets, width: marker.title == 'Paris' || street ? 1 : 2 }); }); } + /* + Next, we draw all avenues from 20 avenues "east" to 20 avenues + "west" of the point. + */ Ox.loop(-20, 20, function(avenue) { var line = getLine(g, marker.point, as, 'avenues', avenue); image.drawPath(line, { @@ -405,21 +461,33 @@ Ox.load('Image', function() { width: marker.title == 'Paris' || avenue ? 1 : 2 }); }); + /* + Then, on the Manhattan tile, we draw Broadway, too. + */ if (marker.title == 'Manhattan') { var line = mapLine(Ox.getLine( Ox.getPoint(marker.point, -10000, bearings.broadway), Ox.getPoint(marker.point, 10000, bearings.broadway), 1 ), g); - image.drawPath(line, {color: 'rgba(0, 0, 255, 0.5)', width: 2}); + image.drawPath(line, { + color: 'rgba(0, 0, 255, 0.5)', + width: 2 + }); } + /* + Finally, we add the place name (white text with a black shadow). + */ ['black', 'white'].forEach(function(color, i) { image.drawText(marker.title, [240 - i, 240 - i], { color: color, font: 'bold 16px Lucida Grande, sans-serif', textAlign: 'right' }); - }) + }); + /* + Now we can put the image into the DOM. + */ $images[i] = Ox.$('') .attr({src: image.src()}) .hide() @@ -429,6 +497,31 @@ Ox.load('Image', function() { }); + /* + `getIntersection` is a helper function that returns the coordinates of the + closest intersection from a given point. + */ + function getIntersection(point) { + var as = getASByLatLng(point), d = {}; + ['avenue', 'street'].forEach(function(type) { + var mod = Ox.mod(as[type], 1); + d[type] = ((mod < 0.5 ? 0 : 1) - mod) * distances[type + 's']; + }); + return Ox.getPoint( + Ox.getPoint( + point, + d.street, + as.bearings.avenues + ), + d.avenue, + as.bearings.streets + ); + } + + /* + `getLine` is a helper function that returns the i-th avenue or street from a + given point, as an array of x/y coordinates on the zoomed-in map tile. + */ function getLine(g, point, as, type, i) { point = Ox.getPoint( point, @@ -442,6 +535,12 @@ Ox.load('Image', function() { ), g); } + /* + `mapLine` is a helper function that, given a line of points (an array of + lat/lng pairs) and an object `g` with the properties `lat`, `lng`, `s` + (tile size) and `z` (zoom level), maps the line to an array of x/y + coordinates on the zoomed-in map tile. + */ function mapLine(line, g) { return line.map(function(point) { var xy = Ox.map(Ox.getXYByLatLng(point), function(value, key) { @@ -451,7 +550,12 @@ Ox.load('Image', function() { }); } - function click(e) { + /* + Now all that's left is adding our event handlers. Clicking on any point of + the map that is not a marker will hide the currently visible overlay image + (if any). + */ + function onClick(e) { if (e.target.className != 'marker') { $images.forEach(function($image) { $image.hide(); @@ -459,23 +563,46 @@ Ox.load('Image', function() { } } - function mouseover() { + /* + When the mouse enters the map, show a street sign (which consists of a post + an the actual sign). + */ + function onMouseover() { $post.show(); $sign.show(); } - function mousemove(e) { + /* + When the mouse moves on the map, update the street sign. + */ + function onMousemove(e) { + /* + In case the mouse is on the overlay image, hide the sign and return. + */ if (e.target.tagName == 'IMG') { - mouseout(); + onMouseout(); return; } var left = window.scrollX, right = left + window.innerWidth, top = window.scrollY, + /* + `xy` is the actual map pixel the mouse is pointing at... + */ xy = {x: left + e.clientX, y: top + e.clientY}, + /* + ... `latlng` is the latitude and longitude of that point ... + */ latlng = getLatLngByXY(xy), + /* + ... and `as` is the equivalent in avenues and streets. + */ as = getASByXY(xy), width, height, invertX, invertY; + /* + On the street sign, we display both the avenue/street and the lat/lng + coordinates. + */ $sign.html( Ox.formatNumber(as.avenue, 0) + 'th Av & ' + as.hemisphere + ' ' @@ -484,7 +611,12 @@ Ox.load('Image', function() { + Ox.formatDegrees(latlng.lat, 'lat') + ' / ' + Ox.formatDegrees(((latlng.lng + 180) % 360) - 180, 'lng') + '
' - ) + ); + /* + As the street sign extends to the top and right of the mouse position, + we have to invert its direction in case the mouse is close to the top or + right edge of the window. + */ width = $sign.width(); height = $sign.height(); invertX = xy.x + width > right; @@ -496,13 +628,19 @@ Ox.load('Image', function() { $post.css({ left: xy.x - 1 + 'px', top: xy.y + (invertY ? 0 : -32 - height) + 'px', - height: $sign.height() + 32 + 'px' + height: height + 32 + 'px' }); } - function mouseout() { + /* + When the mouse leaves the map, hide the street sign. + */ + function onMouseout() { $post.hide(); $sign.hide(); } + /* + And that's it! + */ }); \ No newline at end of file