update manhattan grid example
This commit is contained in:
parent
a04eb376ed
commit
d07c8d44e3
1 changed files with 173 additions and 35 deletions
|
@ -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
|
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,
|
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
|
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
|
intersection of Zero & Zero. From there, Broadway, Zero Ave and Zero St continue
|
||||||
continue as perfectly straight equatorial lines. All three will intersect once
|
as perfectly straight equatorial lines. All three will intersect once more,
|
||||||
more, exactly halfway, in the Indian Ocean, (southwest of Australia), at the
|
exactly halfway, in the Indian Ocean, (southwest of Australia), at the point
|
||||||
point furthest from Manhattan.
|
furthest from Manhattan.
|
||||||
|
|
||||||
As subsequent avenues remain exactly parallel to Zero Ave, and subsequent
|
As subsequent avenues remain exactly parallel to Zero Ave, and subsequent
|
||||||
streets exactly parallel to Zero St, they form smaller and smaller circles
|
streets exactly parallel to Zero St, they form smaller and smaller circles
|
||||||
|
@ -121,8 +121,8 @@ Ox.load('Image', function() {
|
||||||
bearings.streets
|
bearings.streets
|
||||||
);
|
);
|
||||||
/*
|
/*
|
||||||
The second intersection of Zero Avenue, Zero Street and Broadway is half of
|
The second intersection of Zero Ave, Zero St and Broadway is half of the
|
||||||
the Earth's circumference away from the first one, in any direction.
|
Earth's circumference away from the first one, in any direction.
|
||||||
*/
|
*/
|
||||||
points['-0 & -0'] = Ox.getPoint(
|
points['-0 & -0'] = Ox.getPoint(
|
||||||
points['0 & 0'],
|
points['0 & 0'],
|
||||||
|
@ -279,7 +279,7 @@ Ox.load('Image', function() {
|
||||||
function drawPath(image, path, options) {
|
function drawPath(image, path, options) {
|
||||||
var n, parts = [[]];
|
var n, parts = [[]];
|
||||||
/*
|
/*
|
||||||
...
|
Close the path by appending the first point.
|
||||||
*/
|
*/
|
||||||
path.push(path[0]);
|
path.push(path[0]);
|
||||||
n = path.length;
|
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) {
|
Ox.Image('jpg/earth1024.jpg', function(image) {
|
||||||
|
|
||||||
mapSize = image.getSize().width;
|
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), {
|
drawPath(image, Ox.getCircle(points['0 & 0'], C / 4, 8), {
|
||||||
color: 'rgba(255, 255, 255, 0.25)'
|
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) {
|
['streets', 'avenues', 'broadway'].forEach(function(type) {
|
||||||
lines[type].forEach(function(line, i) {
|
lines[type].forEach(function(line, i) {
|
||||||
drawPath(image, line, {
|
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({
|
$body.css({
|
||||||
minWidth: mapSize + 'px',
|
minWidth: mapSize + 'px',
|
||||||
height: mapSize + 'px',
|
height: mapSize + 'px',
|
||||||
backgroundImage: 'url(' + image.src() + ')'
|
backgroundImage: 'url(' + image.src() + ')'
|
||||||
})
|
})
|
||||||
.on({
|
.on({
|
||||||
click: click,
|
click: onClick,
|
||||||
mouseover: mouseover,
|
mouseover: onMouseover,
|
||||||
mousemove: mousemove,
|
mousemove: onMousemove,
|
||||||
mouseout: mouseout
|
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: 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}
|
{point: poles.north, title: 'Uzbekistan', z: 14}
|
||||||
].forEach(function(marker, i) {
|
].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),
|
var as = getASByLatLng(marker.point),
|
||||||
g = {s: 256, v: 108, z: marker.z},
|
g = {s: 256, v: 108, z: marker.z},
|
||||||
xy = getXYByLatLng(marker.point);
|
xy = getXYByLatLng(marker.point);
|
||||||
Ox.print(as)
|
|
||||||
Ox.extend(g, Ox.map(Ox.getXYByLatLng(marker.point), function(v) {
|
Ox.extend(g, Ox.map(Ox.getXYByLatLng(marker.point), function(v) {
|
||||||
return Math.floor(v * Math.pow(2, g.z));
|
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.$('<div>')
|
Ox.$('<div>')
|
||||||
.addClass('marker')
|
.addClass('marker')
|
||||||
.css({
|
.css({
|
||||||
|
@ -372,32 +402,58 @@ Ox.load('Image', function() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.appendTo($body);
|
.appendTo($body);
|
||||||
|
/*
|
||||||
|
Now we load the image.
|
||||||
|
*/
|
||||||
Ox.Image(Ox.formatString(
|
Ox.Image(Ox.formatString(
|
||||||
'jpg/v={v}&x={x}&y={y}&z={z}.jpg', g
|
'jpg/v={v}&x={x}&y={y}&z={z}.jpg', g
|
||||||
), function(image) {
|
), function(image) {
|
||||||
|
/*
|
||||||
|
First, we draw the streets.
|
||||||
|
*/
|
||||||
if (marker.title == 'Uzbekistan') {
|
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),
|
distances.streets * (numbers.streets % 1),
|
||||||
2000,
|
2000,
|
||||||
distances.streets
|
distances.streets,
|
||||||
).forEach(function(distance) {
|
function(distance) {
|
||||||
var circle = mapLine(Ox.getCircle(
|
var circle = mapLine(Ox.getCircle(
|
||||||
poles.north, distance, precision
|
poles.north, distance, precision
|
||||||
), g);
|
), g);
|
||||||
image.drawPath(circle, {
|
image.drawPath(circle, {
|
||||||
close: true,
|
close: true,
|
||||||
color: colors.streets
|
color: colors.streets
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} else {
|
} 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) {
|
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, {
|
image.drawPath(line, {
|
||||||
color: colors.streets,
|
color: colors.streets,
|
||||||
width: marker.title == 'Paris' || street ? 1 : 2
|
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) {
|
Ox.loop(-20, 20, function(avenue) {
|
||||||
var line = getLine(g, marker.point, as, 'avenues', avenue);
|
var line = getLine(g, marker.point, as, 'avenues', avenue);
|
||||||
image.drawPath(line, {
|
image.drawPath(line, {
|
||||||
|
@ -405,21 +461,33 @@ Ox.load('Image', function() {
|
||||||
width: marker.title == 'Paris' || avenue ? 1 : 2
|
width: marker.title == 'Paris' || avenue ? 1 : 2
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
|
Then, on the Manhattan tile, we draw Broadway, too.
|
||||||
|
*/
|
||||||
if (marker.title == 'Manhattan') {
|
if (marker.title == 'Manhattan') {
|
||||||
var line = mapLine(Ox.getLine(
|
var line = mapLine(Ox.getLine(
|
||||||
Ox.getPoint(marker.point, -10000, bearings.broadway),
|
Ox.getPoint(marker.point, -10000, bearings.broadway),
|
||||||
Ox.getPoint(marker.point, 10000, bearings.broadway),
|
Ox.getPoint(marker.point, 10000, bearings.broadway),
|
||||||
1
|
1
|
||||||
), g);
|
), 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) {
|
['black', 'white'].forEach(function(color, i) {
|
||||||
image.drawText(marker.title, [240 - i, 240 - i], {
|
image.drawText(marker.title, [240 - i, 240 - i], {
|
||||||
color: color,
|
color: color,
|
||||||
font: 'bold 16px Lucida Grande, sans-serif',
|
font: 'bold 16px Lucida Grande, sans-serif',
|
||||||
textAlign: 'right'
|
textAlign: 'right'
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
/*
|
||||||
|
Now we can put the image into the DOM.
|
||||||
|
*/
|
||||||
$images[i] = Ox.$('<img>')
|
$images[i] = Ox.$('<img>')
|
||||||
.attr({src: image.src()})
|
.attr({src: image.src()})
|
||||||
.hide()
|
.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) {
|
function getLine(g, point, as, type, i) {
|
||||||
point = Ox.getPoint(
|
point = Ox.getPoint(
|
||||||
point,
|
point,
|
||||||
|
@ -442,6 +535,12 @@ Ox.load('Image', function() {
|
||||||
), g);
|
), 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) {
|
function mapLine(line, g) {
|
||||||
return line.map(function(point) {
|
return line.map(function(point) {
|
||||||
var xy = Ox.map(Ox.getXYByLatLng(point), function(value, key) {
|
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') {
|
if (e.target.className != 'marker') {
|
||||||
$images.forEach(function($image) {
|
$images.forEach(function($image) {
|
||||||
$image.hide();
|
$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();
|
$post.show();
|
||||||
$sign.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') {
|
if (e.target.tagName == 'IMG') {
|
||||||
mouseout();
|
onMouseout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var left = window.scrollX,
|
var left = window.scrollX,
|
||||||
right = left + window.innerWidth,
|
right = left + window.innerWidth,
|
||||||
top = window.scrollY,
|
top = window.scrollY,
|
||||||
|
/*
|
||||||
|
`xy` is the actual map pixel the mouse is pointing at...
|
||||||
|
*/
|
||||||
xy = {x: left + e.clientX, y: top + e.clientY},
|
xy = {x: left + e.clientX, y: top + e.clientY},
|
||||||
|
/*
|
||||||
|
... `latlng` is the latitude and longitude of that point ...
|
||||||
|
*/
|
||||||
latlng = getLatLngByXY(xy),
|
latlng = getLatLngByXY(xy),
|
||||||
|
/*
|
||||||
|
... and `as` is the equivalent in avenues and streets.
|
||||||
|
*/
|
||||||
as = getASByXY(xy),
|
as = getASByXY(xy),
|
||||||
width, height, invertX, invertY;
|
width, height, invertX, invertY;
|
||||||
|
/*
|
||||||
|
On the street sign, we display both the avenue/street and the lat/lng
|
||||||
|
coordinates.
|
||||||
|
*/
|
||||||
$sign.html(
|
$sign.html(
|
||||||
Ox.formatNumber(as.avenue, 0) + 'th Av & '
|
Ox.formatNumber(as.avenue, 0) + 'th Av & '
|
||||||
+ as.hemisphere + ' '
|
+ as.hemisphere + ' '
|
||||||
|
@ -484,7 +611,12 @@ Ox.load('Image', function() {
|
||||||
+ Ox.formatDegrees(latlng.lat, 'lat') + ' / '
|
+ Ox.formatDegrees(latlng.lat, 'lat') + ' / '
|
||||||
+ Ox.formatDegrees(((latlng.lng + 180) % 360) - 180, 'lng')
|
+ Ox.formatDegrees(((latlng.lng + 180) % 360) - 180, 'lng')
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
)
|
);
|
||||||
|
/*
|
||||||
|
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();
|
width = $sign.width();
|
||||||
height = $sign.height();
|
height = $sign.height();
|
||||||
invertX = xy.x + width > right;
|
invertX = xy.x + width > right;
|
||||||
|
@ -496,13 +628,19 @@ Ox.load('Image', function() {
|
||||||
$post.css({
|
$post.css({
|
||||||
left: xy.x - 1 + 'px',
|
left: xy.x - 1 + 'px',
|
||||||
top: xy.y + (invertY ? 0 : -32 - height) + '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();
|
$post.hide();
|
||||||
$sign.hide();
|
$sign.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
And that's it!
|
||||||
|
*/
|
||||||
});
|
});
|
Loading…
Reference in a new issue