Some more work on the "Articles nearby feature", including adding a few functions in geometry.js (with corresponding unit tests)

This commit is contained in:
mossroy 2013-11-22 16:37:16 +01:00
parent 46257816cf
commit 20f213abd5
3 changed files with 447 additions and 326 deletions

View File

@ -137,7 +137,7 @@ define(function(require) {
var filerequest = storage.get(directory + 'coordinates_' + prefixedFileNumber
+ '.idx');
filerequest.onsuccess = function() {
currentLocalArchiveInstance.coordinateFiles[index] = filerequest.result;
currentLocalArchiveInstance.coordinateFiles[index - 1] = filerequest.result;
currentLocalArchiveInstance.readCoordinateFilesFromStorage(storage, directory,
index + 1);
};
@ -218,7 +218,7 @@ define(function(require) {
var coordinateFileNr = coordinateFileRegex.exec(file.name);
if (coordinateFileNr && coordinateFileNr.length > 0) {
var intFileNr = 1 * coordinateFileNr[1];
this.coordinateFiles[intFileNr] = file;
this.coordinateFiles[intFileNr - 1] = file;
}
else {
var dataFileNr = dataFileRegex.exec(file.name);
@ -725,69 +725,110 @@ define(function(require) {
LocalArchive.prototype.getTitlesInCoords = function(rect, maxTitles, callbackFunction) {
var normalizedRectangle = rect.normalized();
var i = 0;
LocalArchive.getTitlesInCoordsInt(this, i, 0, normalizedRectangle, GLOBE_RECTANGLE, maxTitles, new Array(), callbackFunction, this.callbackGetTitlesInCoordsInt);
LocalArchive.getTitlesInCoordsInt(this, i, 0, normalizedRectangle, GLOBE_RECTANGLE, maxTitles, new Array(), callbackFunction, LocalArchive.callbackGetTitlesInCoordsInt);
};
LocalArchive.callbackGetTitlesInCoordsInt = function(localArchive, titlesFound, i, maxTitles, normalizedRectangle, callbackFunction) {
i++;
if (titlesFound.length < maxTitles && i < localArchive.coordinateFiles.length) {
LocalArchive.getTitlesInCoordsInt(localArchive, i, 0, normalizedRectangle, GLOBE_RECTANGLE, maxTitles, titlesFound, callbackFunction, this.callbackGetTitlesInCoordsInt);
LocalArchive.getTitlesInCoordsInt(localArchive, i, 0, normalizedRectangle, GLOBE_RECTANGLE, maxTitles, titlesFound, callbackFunction, LocalArchive.callbackGetTitlesInCoordsInt);
}
else {
callbackFunction(titlesFound);
}
};
LocalArchive.getTitlesInCoordsInt = function(localArchive, coordinateFileIndex, coordFilePos, targetRect, thisRect, maxTitles, titlesFound, callbackFunction, callbackGetTitlesInCoordsInt) {
// TODO : as reading a file is asynchronous, this function needs to be split
// into several callback functions
/**
* Reads 4 bytes in given byteArray, starting at startIndex, and convert
* these 4 bytes into latitude and longitude (each uses 2 bytes, little endian)
* @param {type} byteArray
* @param {type} startIndex
* @returns {_L23.geometry.point}
*/
readCoordinates = function(byteArray, startIndex) {
var lat = byteArray[startIndex] + 256 * byteArray[startIndex + 1];
var long = byteArray[startIndex + 2] + 256 * byteArray[startIndex + 3];
var point = new geometry.point(long, lat);
return point;
};
LocalArchive.getTitlesInCoordsInt = function(localArchive, coordinateFileIndex, coordFilePos, targetRect, thisRect, maxTitles, titlesFound, callbackFunction, callbackGetTitlesInCoordsInt) {
var reader = new FileReader();
reader.onerror = errorHandler;
reader.onabort = function(e) {
alert('Coordinate file read cancelled');
};
reader.onload = function(e) {
var binaryTitleFile = e.target.result;
var byteArray = new Uint8Array(binaryTitleFile);
// Compute selector
var selector = byteArray[0] + 256 * byteArray[1];
// read this.coordinateFiles[coordinateFileIndex] at coordFilePos
// retrieve selector
var selector;
// 0xFFFF = 65535 in decimal
if (selector === 65535) {
// not enough articles, further subdivision needed
// read coordinates in coordinateFile
// compute the 4 rectangles and 4 positions
var rectSW;
var pos0;
var rectSE;
var pos1;
var rectNW;
var pos2;
var rectNE;
var pos3;
if (targetRect.intersects(rectSW)) {
var center = readCoordinates(byteArray, 2);
var lensw = byteArray[10] + 256 * byteArray[11] + 256 * 256 * byteArray[12] + 256 * 256 * 256 * byteArray[13];
var lense = byteArray[14] + 256 * byteArray[15] + 256 * 256 * byteArray[16] + 256 * 256 * 256 * byteArray[17];
var lennw = byteArray[18] + 256 * byteArray[19] + 256 * 256 * byteArray[20] + 256 * 256 * 256 * byteArray[21];
// compute the 4 positions
var pos0 = coordFilePos + 22;
var pos1 = pos0 + lensw;
var pos2 = pos1 + lense;
var pos3 = pos2 + lennw;
// compute the 4 rectangles
var rectSW = new geometry.rect(thisRect.origin(), center);
var rectSE = (new geometry.rect(thisRect.topRight(), center)).normalized();
var rectNW = (new geometry.rect(thisRect.bottomLeft(), center)).normalized();
var rectNE = (new geometry.rect(thisRect.corner(), center)).normalized();
if (targetRect.intersect(rectSW)) {
LocalArchive.getTitlesInCoordsInt(localArchive, coordinateFileIndex, pos0, targetRect, rectSW, maxTitles, titlesFound, callbackFunction, callbackGetTitlesInCoordsInt);
}
if (targetRect.intersects(rectSE)) {
if (targetRect.intersect(rectSE)) {
LocalArchive.getTitlesInCoordsInt(localArchive, coordinateFileIndex, pos1, targetRect, rectSE, maxTitles, titlesFound, callbackFunction, callbackGetTitlesInCoordsInt);
}
if (targetRect.intersects(rectNW)) {
if (targetRect.intersect(rectNW)) {
LocalArchive.getTitlesInCoordsInt(localArchive, coordinateFileIndex, pos2, targetRect, rectNW, maxTitles, titlesFound, callbackFunction, callbackGetTitlesInCoordsInt);
}
if (targetRect.intersects(rectNE)) {
if (targetRect.intersect(rectNE)) {
LocalArchive.getTitlesInCoordsInt(localArchive, coordinateFileIndex, pos3, targetRect, rectNE, maxTitles, titlesFound, callbackFunction, callbackGetTitlesInCoordsInt);
}
}
else {
// TODO this part needs to be reworked and does not work as is
// I can not push titles found in the list with a simple for loop because of the asynchronous read
// Maybe I should split that in 2 parts : first gather all the title positions with the loop
// and then read all the titles
for (var i = 0; i < selector; i ++) {
var indexInByteArray = 2 + i * 8;
// Read position (in title file) in coordinateFile
var title_pos;
if (!targetRect.contains(c)) {
var c = readCoordinates(byteArray, indexInByteArray);
var title_pos = byteArray[indexInByteArray + 2] + 256 * byteArray[indexInByteArray + 3] + 256 * 256 * byteArray[indexInByteArray + 4] + 256 * 256 * 256 * byteArray[indexInByteArray + 5];
if (!targetRect.containsPoint(c)) {
continue;
}
// read title at title_pos in title file
var title;
titlesFound.push(title);
localArchive.getTitlesStartingAtOffset(title_pos, 1, function(titles) {
titlesFound.push(titles[0]);
if (maxTitles >= 0 && titlesFound.length >= maxTitles) {
LocalArchive.callbackGetTitlesInCoordsInt(localArchive, titlesFound, coordinateFileIndex, maxTitles, targetRect, callbackFunction);
// TODO : this "return" does not exit from the right function
// I need to find a way to make it return from the onLoad above
return;
}
});
}
LocalArchive.callbackGetTitlesInCoordsInt(localArchive, titlesFound, coordinateFileIndex, maxTitles, targetRect, callbackFunction);
}
};
// Read 22 bytes in the coordinate files, at coordFilePos index, in order to read the selector and the coordinates
// 2 + 4 + 4 + 3 * 4 = 22
var blob = localArchive.coordinateFiles[coordinateFileIndex].slice(coordFilePos, coordFilePos + 22);
// Read in the file as a binary string
reader.readAsArrayBuffer(blob);
};
/**

View File

@ -259,11 +259,22 @@ define(function(require) {
h = x.height;
x = x.x;
}
if (w === undefined && h === undefined) {
// The rectangle is built from topLeft and bottomRight points
var topLeft = x;
var bottomRight = y;
this.x = topLeft.x;
this.y = topLeft.y;
this.width = bottomRight.x - topLeft.x;
this.height = bottomRight.y - topLeft.y;
}
else {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
}
rect.prototype = {
toString: function() {
@ -331,6 +342,49 @@ define(function(require) {
}
return false;
},
// Algorithm copied from java.awt.Rectangle from OpenJDK
// @return {bool} true if rectangle r is inside me
contains: function(r) {
var nr = r.normalized();
var W = nr.width;
var H = nr.height;
var X = nr.x;
var Y = nr.y;
var w = this.width;
var h = this.height;
if ((w | h | W | H) < 0) {
// At least one of the dimensions is negative...
return false;
}
// Note: if any dimension is zero, tests below must return false...
var x = this.x;
var y = this.y;
if (X < x || Y < y) {
return false;
}
w += x;
W += X;
if (W <= X) {
// X+W overflowed or W was zero, return false if...
// either original w or W was zero or
// x+w did not overflow or
// the overflowed x+w is smaller than the overflowed X+W
if (w >= x || W > w) return false;
} else {
// X+W did not overflow and W was not zero, return false if...
// original w was zero or
// x+w did not overflow and x+w is smaller than X+W
if (w >= x && W > w) return false;
}
h += y;
H += Y;
if (H <= Y) {
if (h >= y || H > h) return false;
} else {
if (h >= y && H > h) return false;
}
return true;
},
// @return {point} a point on my boundary nearest to p
// @see Squeak Smalltalk, Rectangle>>pointNearestTo:
pointNearestToPoint: function(p) {

View File

@ -245,17 +245,43 @@ define(function(require) {
&& normalizedRect5.width===4
&& normalizedRect5.height===1, "rect5 successfully normalized by switching bottom right and top left corners");
});
test("check rectangle constructor from top-left and bottom-right points", function() {
var topLeft = new geometry.point(2,3);
var bottomRight = new geometry.point(5,5);
var rect = new geometry.rect(topLeft, bottomRight);
equal(rect.x, 2 , "rect.x should be 2");
equal(rect.y, 3 , "rect.y should be 3");
equal(rect.width, 3 , "rect.width should be 3");
equal(rect.height, 2 , "rect.height should be 2");
});
test("check rectangle contains another rectangle", function() {
var rect1 = new geometry.rect(2,3,4,4);
var rect2 = new geometry.rect(3,4,1,1);
var rect3 = new geometry.rect(1,1,1,1);
var rect4 = new geometry.rect(3,1,2,4);
var rect5 = new geometry.rect(3,1,6,4);
var rect6 = new geometry.rect(2,3,3,2);
var rect7 = new geometry.rect(5,6,-3,-2); // same as rect7 but not normalized
ok(rect1.contains(rect2), "rect1 should contain rect2");
ok(!rect2.contains(rect1), "rect2 should not contain rect1");
ok(!rect1.contains(rect3), "rect1 should not contain rect3");
ok(!rect1.contains(rect4), "rect1 should not contain rect4");
ok(!rect1.contains(rect5), "rect1 should not contain rect5");
ok(rect1.contains(rect1), "rect1 should contain rect1");
ok(rect1.contains(rect6), "rect1 should contain rect6");
ok(rect1.contains(rect7), "rect1 should contain rect7");
});
module("articles_nearby");
asyncTest("check articles found nearby France and Germany", function() {
var callbackTitlesNearbyFound = function(titles) {
ok(titles !== null, "Some titles should be found");
equal(titles.length, 3, "3 titles should be found");
var titleAlps;
var titleAlps = null;
for (var i=0; i<titles.length; i++) {
var title = titles[i];
if (title.name === "Alps") {
titleAlps = title[i];
if (title && title.name === "Alps") {
titleAlps = title;
}
}
ok(titleAlps !== null, "The title 'Alps' should be found");