Skip to content

Commit

Permalink
Improving location math formatting...
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom MacWright committed May 13, 2011
1 parent 0ac9996 commit 5f9c2e1
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 74 deletions.
162 changes: 98 additions & 64 deletions modestmaps.js
Expand Up @@ -223,9 +223,9 @@ if (!com) {
b1 = l1.lon * deg2rad,
a2 = l2.lat * deg2rad,
b2 = l2.lon * deg2rad,
c = Math.cos(a1)*Math.cos(b1)*Math.cos(a2)*Math.cos(b2),
d = Math.cos(a1)*Math.sin(b1)*Math.cos(a2)*Math.sin(b2),
e = Math.sin(a1)*Math.sin(a2);
c = Math.cos(a1) * Math.cos(b1) * Math.cos(a2) * Math.cos(b2),
d = Math.cos(a1) * Math.sin(b1) * Math.cos(a2) * Math.sin(b2),
e = Math.sin(a1) * Math.sin(a2);
return Math.acos(c + d + e) * r;
};

Expand All @@ -240,20 +240,35 @@ if (!com) {
lat2 = l2.lat * deg2rad,
lon2 = l2.lon * deg2rad;

var d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1-lat2)/2),2) + Math.cos(lat1)*Math.cos(lat2)*Math.pow(Math.sin((lon1-lon2)/2),2)));
var bearing = Math.atan2(Math.sin(lon1-lon2)*Math.cos(lat2), Math.cos(lat1)*Math.sin(lat2)-Math.sin(lat1)*Math.cos(lat2)*Math.cos(lon1-lon2)) / -(Math.PI/180);
var d = 2 * Math.asin(
Math.sqrt(
Math.pow(Math.sin((lat1 - lat2) / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
var bearing = Math.atan2(
Math.sin(lon1 - lon2) *
Math.cos(lat2),
Math.cos(lat1) *
Math.sin(lat2) -
Math.sin(lat1) *
Math.cos(lat2) *
Math.cos(lon1 - lon2)
) / -(Math.PI / 180);

bearing = bearing < 0 ? 360 + bearing : bearing;

var A = Math.sin((1-f)*d)/Math.sin(d);
var B = Math.sin(f*d)/Math.sin(d);
var x = A*Math.cos(lat1)*Math.cos(lon1) + B*Math.cos(lat2)*Math.cos(lon2);
var y = A*Math.cos(lat1)*Math.sin(lon1) + B*Math.cos(lat2)*Math.sin(lon2);
var z = A*Math.sin(lat1) + B*Math.sin(lat2);
var x = A * Math.cos(lat1) * Math.cos(lon1) +
B * Math.cos(lat2) * Math.cos(lon2);
var y = A * Math.cos(lat1) * Math.sin(lon1) +
B * Math.cos(lat2) * Math.sin(lon2);
var z = A * Math.sin(lat1) + B * Math.sin(lat2);

var latN = Math.atan2(z,Math.sqrt(Math.pow(x,2)+Math.pow(y,2)));
var latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
var lonN = Math.atan2(y,x);

return new MM.Location(latN/deg2rad, lonN/deg2rad);
return new MM.Location(latN / deg2rad, lonN / deg2rad);
};
// Transformation
// --------------
Expand Down Expand Up @@ -330,7 +345,7 @@ if (!com) {
var a = (((t2 - t3) * (s1 - s2)) - ((t1 - t2) * (s2 - s3)))
/ (((r2 - r3) * (s1 - s2)) - ((r1 - r2) * (s2 - s3)));

var b = (((t2 - t3) * (r1 - r2)) - ((t1 - t2) * (r2 - r3)))
var b = (((t2 - t3) * (r1 - r2)) - ((t1 - t2) * (r2 - r3)))
/ (((s2 - s3) * (r1 - r2)) - ((s1 - s2) * (r2 - r3)));

var c = t1 - (r1 * a) - (s1 * b);
Expand Down Expand Up @@ -509,7 +524,9 @@ if (!com) {
var subdomain = parseInt(coordinate.zoom + coordinate.row + coordinate.column, 10) % subdomains.length;
base = base.replace('{S}', subdomains[subdomain]);
}
return base.replace('{Z}', coordinate.zoom.toFixed(0)).replace('{X}', coordinate.column.toFixed(0)).replace('{Y}', coordinate.row.toFixed(0));
return base.replace('{Z}', coordinate.zoom.toFixed(0))
.replace('{X}', coordinate.column.toFixed(0))
.replace('{Y}', coordinate.row.toFixed(0));
});
};

Expand Down Expand Up @@ -730,16 +747,21 @@ if (!com) {
}
}
};
// RequestManager is an image loading queue
// RequestManager
// --------------
// an image loading queue
MM.RequestManager = function(parent) {

// The loading bay is a document fragment to optimize appending, since
// the elements within are invisible. See
// [this blog post](http://ejohn.org/blog/dom-documentfragments/).
this.loadingBay = document.createDocumentFragment();

this.requestsById = {};
this.openRequestCount = 0;

this.maxOpenRequests = 4;
this.requestQueue = [];
this.requestQueue = [];

this.callbackManager = new MM.CallbackManager(this, [ 'requestcomplete' ]);
};
Expand All @@ -748,17 +770,20 @@ if (!com) {

// DOM element, hidden, for making sure images dispatch complete events
loadingBay: null,

// all known requests, by ID
requestsById: null,

// current pending requests
requestQueue: null,

// current open requests (children of loadingBay)
openRequestCount: null,

// the number of open requests permitted at one time, clamped down
// because of domain-connection limits.
maxOpenRequests: null,

// for dispatching 'requestcomplete'
callbackManager: null,

Expand All @@ -769,17 +794,20 @@ if (!com) {
removeCallback: function(event, callback) {
this.callbackManager.removeCallback(event,callback);
},

dispatchCallback: function(event, message) {
this.callbackManager.dispatchCallback(event,message);
},

// queue management:

// Clear everything in the queue by excluding nothing
clear: function() {
this.clearExcept({});
},


// Clear everything in the queue except for certain keys, speciied
// by an object of the form
//
// { key: throwawayvalue }
clearExcept: function(validKeys) {

// clear things from the queue first...
Expand All @@ -789,21 +817,21 @@ if (!com) {
this.requestQueue[i] = null;
}
}

// then check the loadingBay...
var openRequests = this.loadingBay.childNodes;
for (var j = openRequests.length-1; j >= 0; j--) {
var img = openRequests[j];
if (!(img.id in validKeys)) {
this.loadingBay.removeChild(img);
this.openRequestCount--;
//console.log(this.openRequestCount + " open requests");
/* console.log(this.openRequestCount + " open requests"); */
img.src = img.coord = img.onload = img.onerror = null;
}
}

// hasOwnProperty protects against prototype additions
// "The standard describes an augmentable Object.prototype.
// > "The standard describes an augmentable Object.prototype.
// Ignore standards at your own peril."
// -- http://www.yuiblog.com/blog/2006/09/26/for-in-intrigue/
for (var id in this.requestsById) {
Expand All @@ -818,23 +846,24 @@ if (!com) {
}
}
}

},

// Given a tile key, check whether the RequestManager is currently
// requesting it and waiting for the result.
hasRequest: function(id) {
return (id in this.requestsById);
},

// TODO: remove dependency on coord (it's for sorting, maybe call it data?)
// TODO: rename to requestImage once it's not tile specific
// * TODO: remove dependency on coord (it's for sorting, maybe call it data?)
// * TODO: rename to requestImage once it's not tile specific
requestTile: function(key, coord, url) {
if (!(key in this.requestsById)) {
var request = { key: key, coord: coord.copy(), url: url };
// if there's no url just make sure we don't request this image again
this.requestsById[key] = request;
if (url) {
this.requestQueue.push(request);
//console.log(this.requestQueue.length + ' pending requests');
/* console.log(this.requestQueue.length + ' pending requests'); */
}
}
},
Expand All @@ -850,7 +879,12 @@ if (!com) {
return this._processQueue;
},

// Select images from the `requestQueue` and create image elements for
// them, attaching their load events to the function returned by
// `this.getLoadComplete()` so that they can be added to the map.
processQueue: function(sortFunc) {
// When the request queue fills up beyond 8, start sorting the
// requests so that spiral-loading or another pattern can be used.
if (sortFunc && this.requestQueue.length > 8) {
this.requestQueue.sort(sortFunc);
}
Expand All @@ -859,75 +893,79 @@ if (!com) {
if (request) {

this.openRequestCount++;
//console.log(this.openRequestCount + ' open requests');
/* console.log(this.openRequestCount + ' open requests'); */

// JSLitmus benchmark shows createElement is a little faster than
// new Image() in Firefox and roughly the same in Safari:
// http://tinyurl.com/y9wz2jj http://tinyurl.com/yes6rrt
// http://tinyurl.com/y9wz2jj http://tinyurl.com/yes6rrt
var img = document.createElement('img');

// FIXME: key is technically not unique in document if there
// are two Maps but toKey is supposed to be fast so we're trying
// FIXME: key is technically not unique in document if there
// are two Maps but toKey is supposed to be fast so we're trying
// to avoid a prefix ... hence we can't use any calls to
// document.getElementById() to retrieve images
// `document.getElementById()` to retrieve images
img.id = request.key;
img.style.position = 'absolute';
// FIXME: store this elsewhere to avoid scary memory leaks?
// FIXME: call this 'data' not 'coord' so that RequestManager is less Tile-centric?
img.coord = request.coord;
// * FIXME: store this elsewhere to avoid scary memory leaks?
// * FIXME: call this 'data' not 'coord' so that RequestManager is less Tile-centric?
img.coord = request.coord;

// add it to the DOM in a hidden layer, this is a bit of a hack, but it's
// so that the event we get in image.onload has srcElement assigned in IE6
this.loadingBay.appendChild(img);
// set these before img.src to avoid missing an img that's already cached
// set these before img.src to avoid missing an img that's already cached
img.onload = img.onerror = this.getLoadComplete();
img.src = request.url;

// keep things tidy
request = request.key = request.coord = request.url = null;
}
}
},

_loadComplete: null,


// Get the singleton `_loadComplete` function that is called on image
// load events, either removing them from the queue and dispatching an
// event to add them to the map, or deleting them if the image failed
// to load.
getLoadComplete: function() {
// let's only create this closure once...
if (!this._loadComplete) {
var theManager = this;
this._loadComplete = function(e) {
// this is needed because we don't use MM.addEvent for images
e = e || window.event;

// srcElement for IE, target for FF, Safari etc.
var img = e.srcElement || e.target;

// unset these straight away so we don't call this twice
img.onload = img.onerror = null;
// pull it back out of the (hidden) DOM

// pull it back out of the (hidden) DOM
// so that draw will add it correctly later
theManager.loadingBay.removeChild(img);
theManager.openRequestCount--;
delete theManager.requestsById[img.id];
//console.log(theManager.openRequestCount + ' open requests');

/* console.log(theManager.openRequestCount + ' open requests'); */

// NB:- complete is also true onerror if we got a 404
if (img.complete ||
if (img.complete ||
(img.readyState && img.readyState == 'complete')) {
theManager.dispatchCallback('requestcomplete', img);
}
else {
// if it didn't finish clear its src to make sure it
// if it didn't finish clear its src to make sure it
// really stops loading
// FIXME: we'll never retry because this id is still
// in requestsById - is that right?
img.src = null;
}

// keep going in the same order
// use setTimeout() to avoid the IE recursion limit, see
// use `setTimeout()` to avoid the IE recursion limit, see
// http://cappuccino.org/discuss/2010/03/01/internet-explorer-global-variables-and-stack-overflows/
// and https://github.com/stamen/modestmaps-js/issues/12
setTimeout(theManager.getProcessQueue(), 0);
Expand All @@ -936,7 +974,7 @@ if (!com) {
}
return this._loadComplete;
}

};

// Map
Expand Down Expand Up @@ -1019,11 +1057,11 @@ if (!com) {
} else { // the world
css.appendChild(document.createTextNode(def));
}
//document.getElementsByTagName('head')[0].appendChild(ss1);
this.parent.appendChild(css);
document.getElementsByTagName('head')[0].appendChild(ss1);
this.parent.appendChild(css);
*/

this.requestManager = new MM.RequestManager(this.parent);
this.requestManager = new MM.RequestManager(this.parent);
this.requestManager.addCallback('requestcomplete', this.getTileComplete());

this.layers = {};
Expand Down Expand Up @@ -1223,8 +1261,7 @@ if (!com) {
},

// projecting points on and off screen
coordinatePoint: function(coord)
{
coordinatePoint: function(coord) {
// Return an x, y point on the map image for a given coordinate.
if(coord.zoom != this.coordinate.zoom) {
coord = coord.zoomTo(this.coordinate.zoom);
Expand All @@ -1240,8 +1277,7 @@ if (!com) {

// Get a `MM.Coordinate` from an `MM.Point` - returns a new tile-like object
// from a screen point.
pointCoordinate: function(point)
{
pointCoordinate: function(point) {
// new point coordinate reflecting distance from map center, in tile widths
var coord = this.coordinate.copy();
coord.column += (point.x - this.dimensions.x/2) / this.provider.tileWidth;
Expand All @@ -1251,14 +1287,12 @@ if (!com) {
},

// Return an x, y point on the map image for a given geographical location.
locationPoint: function(location)
{
locationPoint: function(location) {
return this.coordinatePoint(this.provider.locationCoordinate(location));
},

// Return a geographical location on the map image for a given x, y point.
pointLocation: function(point)
{
pointLocation: function(point) {
return this.provider.coordinateLocation(this.pointCoordinate(point));
},

Expand Down

0 comments on commit 5f9c2e1

Please sign in to comment.