Quad tree and Kd tree - c#

I have a set of latitude and longitude for various locations and also know the latitude and longitude of my current location. I have to find out the nearest places from current location.
Which algorithm is best one from Kdtree and quadtree to find out the neighbour locations from the set of latitudes and longitudes?
What are the advantage of one over other?
How can we implement these to algorithms in C# for above purpose?

Comparing spatial indexing techniques I would like to bring a 3rd one into our comparative study which is called Grid indexing. and in order to understand Quad-Tree I would like to go into Grid indexing first.
What is Grid indexing?
Grid indexing is a grid-base spatial indexing method in which the study area is divided into fixed size tiles (fixed dimensions) like chess board.
Using Grid index, every point in a tile are tagged with that tile number, so the Index table could provide you a tag for each point showing the tile which our number falls in.
Imagine a situation in which you need to find points in a given rectangle.
This query is performed in two steps :
Find the tiles that the rectangle is overlapping, and the points in tiles (first filter)
Finding the candidate points in above step that actually lay in our rectangle. this need to be done accurately using points and rectangle coordinates.(second filter)
The first filter creates a set of candidates and prevents to test all points in our study area to be checked one after the other.
The second filter is the accurate check and uses rectangle coordinates to test the candidates.
Now, Take a look at the tiles in above pictures, what happens if the tiles are very big or very Small?
When the tiles are too big, for example assume that you have a tile with equal size of your study area, which makes only one tile! so the first filter is practically useless, the whole processing load will be burden by the second filter. In this case the first filter is fast and the second filter is very slow.
Now imagine that the tiles are very small, in this case the first filter is very slow and practically it generates the answer it self, and the second filter is fast.
Determining the tile size is very important and affects the performance directly but what if you can not determine the best tile dimension? what if you you area has both spare and dense sub-areas?
Here it is the time to use other spatial indexing mechanisms like R-Tree, KD-Tree or Quad-Tree!
What is Quad-Tree?
Quad Tree method starts with a big tile that covers whole study area,and divides it by two horizontal and vertical lines to have four equal area which are new tiles and then inspect each tile to see if it has more than a pre-defined threshold, points in it. in this case the tile will be divided into four equal parts using a horizontal and a vertical dividing lines,again. The process continues till there would be no more tile with number of points bigger than threshold, which is a recursive algorithm.
So in denser areas we have smaller tiles and big tiles when having spare points.
What is KD-Tree?
In KD-Tree, we divide an area if it has more than a threshold points in it (other criterias can be used) dividing is done using a (K-1) dimension geometry, for example in a 3D-Tree we need a plane to divide the space, and in a 2D-Tree we need a line to divide the area.
Dividing geometry is iterative and cyclic, for example in a 3D-Tree, the first splitting plane is a X axis aligned plane and the next dividing plane is Y axis aligned and the next is Z, the cycle continue for each space parts to become acceptable(satisfy the criteria)
The following picture shows a balanced KD-Tree that each dividing line is a median, that divides an area into two sub-area with approximately equal number of points.
Conclusion :
If you have a well-distributed points which is not the case when talking about structural features of earth in a map, cause they are random, but is acceptable when we plan to store a city roads network. I would go for a Grid indexing.
If you have a limited resource(i.e. Car navigation Systems) you need to implement KD-Tree or Quad-Tree. Each has its own pros and cons.
Quad-Tree creates a lot of empty sub-tiles because each tile has to be divided into four parts even if the whole data of our tile can be fit in one quarter, so the rest of sub-tiles are considered redundant.(take a look at the Quad-Tree picture at above)
Quad-Tree has much more easier index and can be implemented more easier. Accessing a tile with a Tile ID does not need a recursive function.
In a 2 dimensional Kd-Tree, each node has just two children nodes or has no child at all, so searching through the KD-Tree is a binary search in nature.
Updating Quad-Tree is much more easier than updating a balanced KD-Tree.
With the above descriptions I recommend to start with Quad-Tree
Here it is a sample code for quad-tree that intend to create 5000 random point.
#include<stdio.h>
#include<stdlib.h>
//Removed windows-specific header and functions
//-------------------------------------
// STRUCTURES
//-------------------------------------
struct Point
{
int x;
int y;
};
struct Node
{
int posX;
int posY;
int width;
int height;
Node *child[4]; //Changed to Node *child[4] rather than Node ** child[4]
Point pointArray[5000];
};
//-------------------------------------
// DEFINITIONS
//-------------------------------------
void BuildQuadTree(Node *n);
void PrintQuadTree(Node *n, int depth = 0);
void DeleteQuadTree(Node *n);
Node *BuildNode(Node *n, Node *nParent, int index);
//-------------------------------------
// FUNCTIONS
//-------------------------------------
void setnode(Node *xy,int x, int y, int w, int h)
{
int i;
xy->posX = x;
xy->posY = y;
xy->width= w;
xy->height= h;
for(i=0;i<5000;i++)
{
xy->pointArray[i].x=560;
xy->pointArray[i].y=560;
}
//Initialises child-nodes to NULL - better safe than sorry
for (int i = 0; i < 4; i++)
xy->child[i] = NULL;
}
int randn()
{
int a;
a=rand()%501;
return a;
}
int pointArray_size(Node *n)
{
int m = 0,i;
for (i = 0;i<=5000; i++)
if(n->pointArray[i].x <= 500 && n->pointArray[i].y <= 500)
m++;
return (m + 1);
}
//-------------------------------------
// MAIN
//-------------------------------------
int main()
{
// Initialize the root node
Node * rootNode = new Node; //Initialised node
int i, x[5000],y[5000];
FILE *fp;
setnode(rootNode,0, 0, 500, 500);
// WRITE THE RANDOM POINT FILE
fp = fopen("POINT.C","w");
if ( fp == NULL )
{
puts ( "Cannot open file" );
exit(1);
}
for(i=0;i<5000;i++)
{
x[i]=randn();
y[i]=randn();
fprintf(fp,"%d,%d\n",x[i],y[i]);
}
fclose(fp);
// READ THE RANDOM POINT FILE AND ASSIGN TO ROOT Node
fp=fopen("POINT.C","r");
for(i=0;i<5000;i++)
{
if(fscanf(fp,"%d,%d",&x[i],&y[i]) != EOF)
{
rootNode->pointArray[i].x=x[i];
rootNode->pointArray[i].y=y[i];
}
}
fclose(fp);
// Create the quadTree
BuildQuadTree(rootNode);
PrintQuadTree(rootNode); //Added function to print for easier debugging
DeleteQuadTree(rootNode);
return 0;
}
//-------------------------------------
// BUILD QUAD TREE
//-------------------------------------
void BuildQuadTree(Node *n)
{
Node * nodeIn = new Node; //Initialised node
int points = pointArray_size(n);
if(points > 100)
{
for(int k =0; k < 4; k++)
{
n->child[k] = new Node; //Initialised node
nodeIn = BuildNode(n->child[k], n, k);
BuildQuadTree(nodeIn);
}
}
}
//-------------------------------------
// PRINT QUAD TREE
//-------------------------------------
void PrintQuadTree(Node *n, int depth)
{
for (int i = 0; i < depth; i++)
printf("\t");
if (n->child[0] == NULL)
{
int points = pointArray_size(n);
printf("Points: %d\n", points);
return;
}
else if (n->child[0] != NULL)
{
printf("Children:\n");
for (int i = 0; i < 4; i++)
PrintQuadTree(n->child[i], depth + 1);
return;
}
}
//-------------------------------------
// DELETE QUAD TREE
//-------------------------------------
void DeleteQuadTree(Node *n)
{
if (n->child[0] == NULL)
{
delete n;
return;
}
else if (n->child[0] != NULL)
{
for (int i = 0; i < 4; i++)
DeleteQuadTree(n->child[i]);
return;
}
}
//-------------------------------------
// BUILD NODE
//-------------------------------------
Node *BuildNode(Node *n, Node *nParent, int index)
{
int numParentPoints, i,j = 0;
// 1) Creates the bounding box for the node
// 2) Determines which points lie within the box
/*
Position of the child node, based on index (0-3), is determined in this order:
| 1 | 0 |
| 2 | 3 |
*/
setnode(n, 0, 0, 0, 0);
switch(index)
{
case 0: // NE
n->posX = nParent->posX+nParent->width/2;
n->posY = nParent->posY+nParent->height/2;
break;
case 1: // NW
n->posX = nParent->posX;
n->posY = nParent->posY+nParent->height/2;
break;
case 2: // SW
n->posX = nParent->posX;
n->posY = nParent->posY;
break;
case 3: // SE
n->posX = nParent->posX+nParent->width/2;
n->posY = nParent->posY;
break;
}
// Width and height of the child node is simply 1/2 of the parent node's width and height
n->width = nParent->width/2;
n->height = nParent->height/2;
// The points within the child node are also based on the index, similiarily to the position
numParentPoints = pointArray_size(nParent);
switch(index)
{
case 0: // NE
for(i = 0; i < numParentPoints-1; i++)
{
// Check all parent points and determine if it is in the top right quadrant
if(nParent->pointArray[i].x<=500 && nParent->pointArray[i].x > nParent->posX+nParent->width/2 && nParent->pointArray[i].y > nParent->posY + nParent->height/2 && nParent->pointArray[i].x <= nParent->posX + nParent->width && nParent->pointArray[i].y <= nParent->posY + nParent-> height)
{
// Add the point to the child node's point array
n->pointArray[j].x = nParent ->pointArray[i].x;
n->pointArray[j].y = nParent ->pointArray[i].y;
j++;
}
}
break;
case 1: // NW
for(i = 0; i < numParentPoints-1; i++)
{
// Check all parent points and determine if it is in the top left quadrant
if(nParent->pointArray[i].x<=500 && nParent->pointArray[i].x > nParent->posX && nParent->pointArray[i].y > nParent->posY+ nParent-> height/2 && nParent->pointArray[i].x <= nParent->posX + nParent->width/2 && nParent->pointArray[i].y <= nParent->posY + nParent->height)
{
// Add the point to the child node's point array
n->pointArray[j].x = nParent ->pointArray[i].x;
n->pointArray[j].y = nParent ->pointArray[i].y;
j++;
}
}
break;
case 2: // SW
for(i = 0; i < numParentPoints-1; i++)
{
// Check all parent points and determine if it is in the bottom left quadrant
if(nParent->pointArray[i].x<=500 && nParent->pointArray[i].x > nParent->posX && nParent->pointArray[i].y > nParent->posY && nParent->pointArray[i].x <= nParent->posX + nParent->width/2 && nParent->pointArray[i].y <= nParent->posY + nParent->height/2)
{
// Add the point to the child node's point array
n->pointArray[j].x = nParent ->pointArray[i].x;
n->pointArray[j].y = nParent ->pointArray[i].y;
j++;
}
}
break;
case 3: // SE
for(i = 0; i < numParentPoints-1; i++)
{
// Check all parent points and determine if it is in the bottom right quadrant
if(nParent->pointArray[i].x<=500 && nParent->pointArray[i].x > nParent->posX + nParent->width/2 && nParent->pointArray[i].y > nParent->posY && nParent->pointArray[i].x <= nParent->posX + nParent->width && nParent->pointArray[i].y <= nParent->posY + nParent->height/2)
{
// Add the point to the child node's point array
n->pointArray[j].x = nParent ->pointArray[i].x;
n->pointArray[j].y = nParent ->pointArray[i].y;
j++;
}
}
break;
}
return n;
}

I'd argue that in this case a kd-tree would do better than a quadtree, since when using a quadtree, for finding a nearest neighbor, the closest object might be placed right on the other side of a division between nodes. Kd-trees on the other hand, allows implementation of very efficient nearest-neighbor search, although insertion and removal will be more difficult, while maintaining a balanced tree.

There are a couple of logic errors:
for(i = 0; i <= numParentPoints-1; i++)
return m;

Related

HackerRank Climbing the Leaderboard

This question has to do with this challenge on HackerRank. It seems to be failing some cases, but I'm not clear what's wrong with the algorithm (many people seem to have problem with timeouts, that's not an issue here, everything runs plenty fast, and all the cases that are visible to me pass, so I don't have a specific case that's failing).
The essential outline of how the algorithm works is as follows:
First be sure that Alice isn't already winning over the existing highest score (degenerate case), if she is just tell the world she's #1 from start to finish. Otherwise, at least one score on the leaderboard beats Alice's first try.
Start by walking down the scores list from the highest until we find a place where Alice fits in and record the scores that beat Alice's initial score along the way.
If we reach the end of the scores list before finding a place for Alice's bottom score, pretend there is a score at the bottom of the list which matches Alice's first score (this is just convenient for the main loop and reduces the problem to one where Alice's first score is on the list somewhere)
At this point we have a (sorted) array of scores with their associated ranks, rankAry[r - 1] is the minimum score needed for Alice to attain rank r as of the end of the if clause following the first while loop.
From there, the main algorithm takes over where we walk through Alice's scores and note her rank as we go by comparing against the benchmarks from the scores array that we setup as rankAry earlier. curRank is our candidate rank at each stage which we've definitely achieved by the time this loop starts (by construction).
If we're at rank 1 we will be forever more, so just populate the current rank as 1 and move on.
If we're currently tied with or beating the current benchmark and that's not the end of the line, keep peeking at the next one and if we're also beating that next one, decrease the current benchmark location and iterate
Once this terminates, we've found the one we're going to supplant and we cannot supplant anything further, so assign this rank to this score and repeat until done
As far as I can tell this handles all cases correctly, even if Alice has repeated values or increases between the benchmarks from scores, we should stay at the same rank until we hit the new benchmarks, but the site feedback indicates there must be a bug somewhere.
All the other approaches I've been able to find seem to be some variation on doing a binary search to find the score each time, but I prefer not having to constantly search each time and just use the auxiliary space, so I'm a little stumped on what could be off.
static int[] climbingLeaderboard(int[] scores, int[] alice) {
int[] res = new int[alice.Length];
if (scores.Length == 0 || alice[0] >= scores[0]) { //degenerate cases
for (int i = 0; i < alice.Length; ++i) {
res[i] = 1;
}
return res;
}
int[] rankAry = new int[scores.Length + 1];
rankAry[0] = scores[0]; //top score rank
int curPos = 1; //start at the front and move down
int curRank = 1; //initialize
//initialize from the front. This way we can figure out ranks as we go
while (curPos < scores.Length && scores[curPos] > alice[0]) {
if (scores[curPos] < scores[curPos-1]) {
rankAry[curRank] = scores[curPos]; //update the rank break point
curRank++; //moved down in rank
}
curPos++; //move down the array
}
if (curPos == scores.Length) { //smallest score still bigger than Alice's first
rankAry[curRank] = alice[0]; //pretend there was a virtual value at the end
curRank++; //give rank Alice will have for first score when we get there
}
for (int i = 0; i < alice.Length; ++i) {
if (curRank == 1) { //if we're at the top, we're going to stay there
res[i] = 1;
continue;
}
//Non-degenerate cases
while (alice[i] >= rankAry[curRank - 1]) {
if (curRank == 1 || alice[i] < rankAry[curRank - 2]) {
break;
}
curRank--;
}
res[i] = curRank;
}
return res;
}
You have a couple of bugs in your algorithm.
Wrong mapping
Your rankAry must map a rank (your index) to a score. However, with this line rankAry[0] = scores[0];, the highest score is mapped to 0, but the highest possible rank is 1 and not 0. So, change that to:
rankAry[1] = scores[0];
Wrong initial rank
For some reason, your curRank is set to 1 as below:
int curRank = 1; //initialize
However, it's wrong since your alice[0] is less than scores[0] because of the following block running at the beginning of your method:
if (scores.Length == 0 || alice[0] >= scores[0]) { //degenerate cases
for (int i = 0; i < alice.Length; ++i) {
res[i] = 1;
}
return res;
}
So, at best your curRank is 2. Hence, change it to:
int curRank = 2;
Then, you can also remove curRank++ as your curRank has a correct initial value from:
if (curPos == scores.Length) { //smallest score still bigger than Alice's first
rankAry[curRank] = alice[0]; //pretend there was a virtual value at the end
curRank++; // it's not longer needed so remove it
}
Improve "Non-degenerate cases" handling
Your break condition should consider rankAry at curRank - 1 and not curRank - 2 as it's enough to check the adjacent rank value. Also, a value at curRank - 2 will produce wrong results for some input but I won't explain for which cases specifically - I'll leave it up to you to find out.
Fixed Code
So, I fixed your method according to my comment above and it passed it all the tests. Here it is.
static int[] climbingLeaderboard(int[] scores, int[] alice) {
int[] res = new int[alice.Length];
if (scores.Length == 0 || alice[0] >= scores[0]) { //degenerate cases
for (int i = 0; i < alice.Length; ++i) {
res[i] = 1;
}
return res;
}
int[] rankAry = new int[scores.Length + 1];
rankAry[1] = scores[0]; //top score rank
int curPos = 1; //start at the front and move down
int curRank = 2; //initialize
//initialize from the front. This way we can figure out ranks as we go
while (curPos < scores.Length && scores[curPos] > alice[0]) {
if (scores[curPos] < scores[curPos-1]) {
rankAry[curRank] = scores[curPos]; //update the rank break point
curRank++; //moved down in rank
}
curPos++; //move down the array
}
if (curPos == scores.Length) { //smallest score still bigger than Alice's first
rankAry[curRank] = alice[0]; //pretend there was a virtual value at the end
}
for (int i = 0; i < alice.Length; ++i) {
if (curRank == 1) { //if we're at the top, we're going to stay there
res[i] = 1;
continue;
}
//Non-degenerate cases
while (alice[i] >= rankAry[curRank - 1]) {
if (curRank == 1 || alice[i] < rankAry[curRank - 1]) {
break;
}
curRank--;
}
res[i] = curRank;
}
return res;
}

Split a list into n different sublists matching condition while remaining as close as possible

My question has a lot in common with this one:
Split a list of numbers into n chunks such that the chunks have (close to) equal sums and keep the original order
The main difference is that I have a slightly different metric to figure out which split is "best", and I have an arbitrary condition to respect while doing so.
Every item in my list has two components. Weight and Volume. I have to split them into n different subgroups, while having the total weights of every subgroup as close as possible. The way to test that is simply to get the difference between the heaviest and the lightest subgroup. The smaller this difference is, the better. This means that subgroups [15][15][15][10] are worth the same in final score as subgroups [15][13][11][10].
Then, this is the part I can't figure out how to add into the algorithms proposed as answers to the linked question, I have a hard condition that has to be respected. There is a maximum volume [v] for each subgroup, and none of them can go above it. Going above does not reduce score, it invalidates the entire answer.
How could the algorithms (and code snippets) used as answers to the previous be adapated to take into account the volume condition and the slightly different scoring method?
I am looking for code, pseudo-code or written (detailed) explanation of how this could be done. The question is taggued C# because that's what I'm using, but I am confident that I can translate from any non-esoteric language so feel free to go with whatever you like if you answer with code.
As mentioned in the other question, this problem is very complex and finding the best solution might not be feasible in reasonable computing time, therefore I am looking for an answer that gives a "good enough" solution, even if it might not be the best.
I've formulated a deterministic solution for the given problem using dynamic-programming, sharing the code for the same https://ideone.com/pkfyxg
#include<iostream>
#include<vector>
#include<climits>
#include<cstring>
#include<algorithm>
using namespace std;
// Basic structure for the given problem
struct Item {
float weight;
float volume;
Item(float weight, float volume) {
this->weight = weight;
this->volume = volume;
}
bool operator<(const Item &other) const {
if(weight == other.weight) {
return volume < other.volume;
}
return weight < other.weight;
}
};
// Some constant values
const static int INF = INT_MAX / 100;
const static int MAX_NUM_OF_ITEMS = 1000;
const static int MAX_N = 1000;
// Parameters that we define in main()
float MAX_VOLUME;
vector<Item> items;
// DP lookup tables
int till[MAX_NUM_OF_ITEMS];
float dp[MAX_NUM_OF_ITEMS][MAX_N];
/**
* curIndex: the starting index from where we aim to formulate a new group
* left: number of groups still left to be formed
*/
float solve(int curIndex, int left) {
// Baseline condition
if(curIndex >= items.size() && left == 0) {
return 0;
}
if(curIndex >= items.size() && left != 0) {
return INF;
}
// If we have no more groups to be found, but there still are items left
// then invalidate the solution by returning INF
if(left <= 0 && curIndex < items.size()) {
return INF;
}
// Our lookup dp table
if(dp[curIndex][left] >= 0) {
return dp[curIndex][left];
}
// minVal is the metric to optimize which is the `sum of the differences
// for each group` we intialize it as INF
float minVal = INF;
// The volume of the items we're going to pick for this group
float curVolume = 0;
// Let's try to see how large can this group be by trying to expand it
// one item at a time
for(int i = curIndex; i < items.size(); i++) {
// Verfiy we can put the item i in this group or not
if(curVolume + items[i].volume > MAX_VOLUME) {
break;
}
curVolume += items[i].volume;
// Okay, let's see if it's possible for this group to exist
float val = (items[i].weight - items[curIndex].weight) + solve(i + 1, left - 1);
if(minVal >= val) {
minVal = val;
// The lookup table till tells that the group starting at index
// curIndex, expands till i, i.e. [curIndex, i] is our valid group
till[curIndex] = i + 1;
}
}
// Store the result in dp for memoization and return the value
return dp[curIndex][left] = minVal;
}
int main() {
// The maximum value for Volume
MAX_VOLUME = 6;
// The number of groups we need
int NUM_OF_GROUPS = 5;
items = vector<Item>({
// Item(weight, volume)
Item(5, 2),
Item(2, 1),
Item(10, 3),
Item(7, 2),
Item(3, 1),
Item(5, 3),
Item(4, 3),
Item(3, 2),
Item(10, 1),
Item(11, 3),
Item(19, 1),
Item(21, 2)
});
// Initialize the dp with -1 as default value for unexplored states
memset(dp, -1, sizeof dp);
// Sort the items based on weights first
sort(items.begin(), items.end());
// Solve for the given problem
int val = solve(0, NUM_OF_GROUPS);
// If return value is INF, it means we couldn't distribute it in n
// groups due to the contraint on volume or maybe the number of groups
// was greater than the number of items we had ^_^
if(val >= INF) {
cout << "Not possible to distribute in " << NUM_OF_GROUPS;
return 0;
}
// If a solution exists, use the lookup till array to find which items
// belong to which set
int curIndex = 0, group = 1;
while(curIndex < items.size()) {
cout << "Group #" << group << ": ";
for(int i = curIndex; i < till[curIndex]; i++)
cout << "(" << items[i].weight << ", " << items[i].volume << ") ";
cout << '\n';
group++;
curIndex = till[curIndex];
}
}
I've added comments to the code to help you understand it's working better. The overall runtime complexity for the same is O(num_of_groups * (num_of_items)2) Let me know if you need more explanation around the same ^^;

Changing zoom in links in PDF files

I would like to write some code that changes zoom in links in case of /Fit. I've come up with some code that shows value /Fit and changes it to XYZ 0 0 1 for 100% zoom, but the result PDF has still zoom of type /Fit. Here you can find my sample PDF.
PdfDictionary page = reader.GetPageN(i);
PdfArray annotationsArray = page.GetAsArray(PdfName.ANNOTS);
if (annotationsArray == null)
continue;
for (int j = 0; j < annotationsArray.Size; j++)
{
PdfDictionary annotation = annotationsArray.GetAsDict(j);
PdfDictionary annotationAction = annotation.GetAsDict(PdfName.A);
if (annotationAction == null)
continue;
if (PdfName.GOTO.Equals(annotationAction.Get(PdfName.S)))
{
PdfArray d = annotationAction.GetAsArray(PdfName.D);
if (d.Length == 15)
{
d[1] = new PdfString("XYZ 0 0 1");
//Console.WriteLine(d[1]); // shows /Fit for my sample PDF
}
}
}
EDIT:
Upon a request of #mkl, I paste my code that aims to change zoom in all kind of links in PDF files. By trial-and-error, I've found that arrays that hold information on zoom factor could be of size 15 and 30. Please see my code to find out how I arrived at that numbers. Additionally, Bruno Lowagie has once written, zoom factor of links in PDF could be of 3 and 5 elements.
... zoom factor of destinations that consist of 5 values and that
are of type /XYZ
... [5 0 R, /FitH, 795] has 3 elements (in other words d.size() == 5 is
false)
Sample pdfs
My code:
public static void ChangeZoomOfLinks(PdfReader reader, double zoom = 1)
{
for (int i = 1; i < reader.NumberOfPages; i++)
{
PdfDictionary page = reader.GetPageN(i);
PdfArray annotationsArray = page.GetAsArray(PdfName.ANNOTS);
if (annotationsArray == null)
continue;
for (int j = 0; j < annotationsArray.Size; j++)
{
PdfDictionary annotation = annotationsArray.GetAsDict(j);
PdfDictionary annotationAction = annotation.GetAsDict(PdfName.A);
if (annotationAction == null)
continue;
if (PdfName.GOTO.Equals(annotationAction.Get(PdfName.S)))
{
PdfArray d = annotationAction.GetAsArray(PdfName.D);
if (d == null)
continue;
// for custom zoom type, e.g. 100
if (d.Length == 30) // this length is in: lock.pdf
{
Console.WriteLine("Custom zoom of: {0}. Table length: {1}", d[4], d.Length);
d[4] = new PdfNumber(zoom);
}
// for Fit zoom
else if (d.Length == 15) // this length is in: tony.pdf
{
Console.WriteLine("Custom zoom of: {0}. Table length: {1}", d[1], d.Length);
d[1] = new PdfString("XYZ 0 0 2");
}
}
// below is ported code of Bruno Lowagie
else if (PdfName.LINK.Equals(annotationAction.Get(PdfName.S)))
{
PdfArray d = annotation.GetAsArray(PdfName.DEST);
if (d != null && d.Length == 5 && PdfName.XYZ.Equals(d.GetAsName(1)))
{
//Console.WriteLine(d[4]);
d[4] = new PdfNumber(zoom);
}
}
}
}
}
In a nutshell
The main issue in your code is that you try to set the destination array element d[1] to the string "XYZ 0 0 2" while you should have set d[1] to the name XYZ, d[2] and d[3] to the number 0, and d[4] to the number zoom.
A side issue is that you try to determine the type of the destination array by its Length. This length actually is the number of characters used to represent that array inside the PDF file. This number obviously can vary much, there may be additional white space inside, there may be numbers of arbitrary precision, etc., so this is not a good criterion. What you should use instead is the Size, the number of elements in the array, in combination with the value of the second element.
Fixing your method
Thus, your method can be fixed like this
public static void ChangeZoomOfLinksFixed(PdfReader reader, double zoom = 1)
{
for (int i = 1; i < reader.NumberOfPages; i++)
{
PdfDictionary page = reader.GetPageN(i);
PdfArray annotationsArray = page.GetAsArray(PdfName.ANNOTS);
if (annotationsArray == null)
continue;
for (int j = 0; j < annotationsArray.Size; j++)
{
PdfDictionary annotation = annotationsArray.GetAsDict(j);
PdfDictionary annotationAction = annotation.GetAsDict(PdfName.A);
if (annotationAction == null)
continue;
PdfName actionType = annotationAction.GetAsName(PdfName.S);
PdfArray d = null;
if (PdfName.GOTO.Equals(actionType))
d = annotationAction.GetAsArray(PdfName.D);
else if (PdfName.LINK.Equals(actionType))
d = annotation.GetAsArray(PdfName.DEST);
if (d == null)
continue;
// for custom zoom type
if (d.Size == 5 && PdfName.XYZ.Equals(d.GetAsName(1)))
{
Console.WriteLine("Page {0} {1} XYZ; former zoom {2}.", i, actionType, d[4]);
d[4] = new PdfNumber(zoom);
}
// for Fit zoom
else if (d.Size == 2 && PdfName.FIT.Equals(d.GetAsName(1)))
{
Console.WriteLine("Page {0} {1} Fit.", i, actionType);
d[1] = PdfName.XYZ;
d.Add(new PdfNumber(0));
d.Add(new PdfNumber(0));
d.Add(new PdfNumber(zoom));
}
else if (d.Size > 1)
{
Console.WriteLine("Page {0} {1} {2}. To be implemented.", i, actionType, d.GetAsName(1));
}
else
{
Console.WriteLine("Page {0} {1} {2}. Invalid.", i, actionType, d);
}
}
}
}
As you see I have add additional if branches for invalid destinations (a valid destination array has at least 2 elements) and not yet implemented destination types as there are numerous destination types you do not yet consider.
Specified destination types
The PDF specification knows these destination types:
Syntax Meaning
[page /XYZ left top zoom]
Display the page designated by page, with the coordinates (left, top) positioned at the upper-left corner of the window and the contents of the page magnified by the factor zoom. A null value for any of the parameters left, top, or zoom specifies that the current value of that parameter shall be retained unchanged. A zoom value of 0 has the same meaning as a null value.
[page /Fit]
Display the page designated by page, with its contents magnified just enough to fit the entire page within the window both horizontally and vertically. If the required horizontal and vertical magnification factors are different, use the smaller of the two, centering the page within the window in the other dimension.
[page /FitH top]
Display the page designated by page, with the vertical coordinate top positioned at the top edge of the window and the contents of the page magnified just enough to fit the entire width of the page within the window. A null value for top specifies that the current value of that parameter shall be retained unchanged.
[page /FitV left]
Display the page designated by page, with the horizontal coordinate left positioned at the left edge of the window and the contents of the page magnified just enough to fit the entire height of the page within the window. A null value for left specifies that the current value of that parameter shall be retained unchanged.
[page /FitR left bottom right top]
Display the page designated by page, with its contents magnified just enough to fit the rectangle specified by the coordinates left, bottom, right, and top entirely within the window both horizontally and vertically. If the required horizontal and vertical magnification factors are different, use the smaller of the two, centering the rectangle within the window in the other dimension.
[page /FitB]
(PDF 1.1) Display the page designated by page, with its contents magnified just enough to fit its bounding box entirely within the window both horizontally and vertically. If the required horizontal and vertical magnification factors are different, use the smaller of the two, centering the bounding box within the window in the other dimension.
[page /FitBH top]
(PDF 1.1) Display the page designated by page, with the vertical coordinate top positioned at the top edge of the window and the contents of the page magnified just enough to fit the entire width of its bounding box within the window. A null value for top specifies that the current value of that parameter shall be retained unchanged.
[page /FitBV left]
(PDF 1.1) Display the page designated by page, with the horizontal coordinate left positioned at the left edge of the window and the contents of the page magnified just enough to fit the entire height of its bounding box within the window. A null value for left specifies that the current value of that parameter shall be retained unchanged.
(ISO 32000-1, Table 151 – Destination syntax)
So there still are a number of cases to implement...
Fixing your method even further
As you observed the transformed Fit targets now jumped to the page after the original target if the PageLayout was OneColumn. Everything seemed ok for the PageLayout value SinglePage, though.
The cause is that we insert the coordinates (0,0) for the XYZ replacement of Fit. Looking into the quoted specification excerpt above we see that XYZ means that the page is to be displayed with the given coordinates positioned at the upper-left corner of the window! Usually, though, PDF pages have coordinate systems with the coordinate origin (0,0) in the lower-left corner of the page or even beneath.
Thus, what we interpreted as a jump to the following page actually is a jump where the bottom left of the target page is in the top left of the window... ;)
If we want to use more appropriate coordinates for the top left corner, we can extend the fixed method like this:
public static void ChangeZoomOfLinksFixedMore(PdfReader reader, double zoom = 1)
{
for (int i = 1; i < reader.NumberOfPages; i++)
{
PdfDictionary page = reader.GetPageN(i);
PdfArray annotationsArray = page.GetAsArray(PdfName.ANNOTS);
if (annotationsArray == null)
continue;
for (int j = 0; j < annotationsArray.Size; j++)
{
PdfDictionary annotation = annotationsArray.GetAsDict(j);
PdfDictionary annotationAction = annotation.GetAsDict(PdfName.A);
if (annotationAction == null)
continue;
PdfName actionType = annotationAction.GetAsName(PdfName.S);
PdfArray d = null;
if (PdfName.GOTO.Equals(actionType))
d = annotationAction.GetAsArray(PdfName.D);
else if (PdfName.LINK.Equals(actionType))
d = annotation.GetAsArray(PdfName.DEST);
if (d == null)
continue;
if (d.Size < 2)
{
Console.WriteLine("Page {0} {1} {2}. Invalid.", i, actionType, d);
continue;
}
float left = 0;
float top = 0;
PdfObject pageObject = PdfReader.GetPdfObjectRelease(d[0]);
if (pageObject is PdfDictionary)
{
PdfArray cropBox = ((PdfDictionary)pageObject).GetAsArray(PdfName.CROPBOX);
if (cropBox == null)
cropBox = ((PdfDictionary)pageObject).GetAsArray(PdfName.MEDIABOX);
if (cropBox != null && cropBox.Size == 4)
{
if (cropBox[0] is PdfNumber)
left = ((PdfNumber)cropBox[0]).FloatValue;
if (cropBox[3] is PdfNumber)
top = ((PdfNumber)cropBox[3]).FloatValue;
}
}
// for custom zoom type
if (d.Size == 5 && PdfName.XYZ.Equals(d.GetAsName(1)))
{
Console.WriteLine("Page {0} {1} XYZ; former zoom {2}.", i, actionType, d[4]);
d[4] = new PdfNumber(zoom);
}
// for Fit zoom
else if (d.Size == 2 && PdfName.FIT.Equals(d.GetAsName(1)))
{
Console.WriteLine("Page {0} {1} Fit.", i, actionType);
d[1] = PdfName.XYZ;
d.Add(new PdfNumber(left));
d.Add(new PdfNumber(top));
d.Add(new PdfNumber(zoom));
}
else
{
Console.WriteLine("Page {0} {1} {2}. To be implemented.", i, actionType, d.GetAsName(1));
}
}
}
}
Beware, this does not yet consider all corner cases: The CropBox or MediaBox may be inherited from an ancestor node in the page tree, that box array might not be ordered in the usual manner, or the entries of that array might be indirect objects.

Creating a graph with relative distance (C#)

I have the following problem. I create a chart with migradoc in c#.
Suppose I have the following points for my xAxis:
20.4, 20.6, 30.6, 100.4, 200.3
The problem is that it sets every xpoint in the series on an equal distance in the chart.
While what I need is a graph who sets the xpoints on a relative distance. For example, the distance between points 20.6 and 30.6 needs to be way smaller than the distance between 30.6 and 100.4. (The points always differ, as do the number of points)
One way to make the distance good is to add extra points between the existing points. For example the first step is 0.2 extra, the second step is 10.0 extra. So I want to add for example 50 extra points between this step, so that the distance is relative the same.
This is the only thing I can come up with, can somebody give me some advice how to accomplish this? (Or another possible solution?)
This method worked out for me. I first made the distances relative:
Int64[] relAfstand = new Int64[afstand.Count()];
for(int i = 0; i < afstand.Count(); i++){
double tussenRel = Convert.ToDouble(afstand[i]);
double eindRel = Convert.ToDouble(afstand[afstand.Count()-1]);
double beginRel = Convert.ToDouble(afstand[0]);
double Rel = (((eindRel - beginRel) - (eindRel - tussenRel)) / (eindRel - beginRel));
relAfstand[i] = Convert.ToInt64((Rel)*100);
}
Then I converted the data to scale with relative with the same factor as the distances:
List<double> ConvertedData = new List<double>();
int c = 0;
int c2 = 1;
double steps = 0;
bool calcSteps = false;
bool calcDistance = false;
for (int i = 0; i < 100; i++) {
if (calcDistance == false) {
distance.Add(i);
}
if (relAfstand[c] == i) {
ConvertedData.Add(data[c]);
calcSteps = false;
c2 = 1;
c++;
}else {
if (calcSteps == false) {
steps = ((data[c] - data[c-1])/(relAfstand[c] - relAfstand[c-1]));
calcSteps = true;
}
ConvertedData.Add(data[c-1] + (steps * c2));
c2++;
}
}
calcDistance = true;
Probably not the best workaround, but it works. Since the percentages can come close together I scale both now with around 200-300 instead of 100.

XNA performance

I'm writing some sort of Geometry Wars inspired game except with added 2d rigid body physics Ai pathfinding some waypoint analysis line of sight checks load balancing etc. It seems that even though with around 80-100 enemies on screen it can work reasonably fast with all that stuff enabled the performance completely breaks down once you get to a total of 250 (150 enemies) objects or so. I've searched for any O(n^2) parts in the code but there don't seem to be any left. I'm also using spatial grids.
Even if I disable pretty much everything from the supposedly expensive Ai related processing it doesn't seem to matter, it like still breaks down at 150 enemies.
Now I implemened all the code from scratch, currently even the matrix multiplication code, and I'm almost completely relying on the GC as well as using C# closures for some things, so I expect this to be seriously far from being optimized, but still it doesn't make sense to me that with like 1/15 of the processing work but double the objects the game suddenly starts to slow down to crawl? Is this normal, how is the XNA platform normally supposed to scale as far as the amount of objects being processed is concerned?
I remember Some slerp spinning cube thing I did at first could handle more than 1000 at once so I think I'm doing something wrong?
edit:
Here's the grid structure's class
public abstract class GridBase{
public const int WORLDHEIGHT = (int)AIGridInfo.height;
public const int WORLDWIDTH = (int)AIGridInfo.width;
protected float cellwidth;
protected float cellheight;
int no_of_col_types;
// a dictionary of lists that gets cleared every frame
// 3 (=no_of_col_types) groups of objects (enemy side, players side, neutral)
// 4000 initial Dictionary hash positions for each group
// I have also tried using an array of lists of 100*100 cells
//with pretty much identical results
protected Dictionary<CoordsInt, List<Collidable>>[] grid;
public GridBase(float cellwidth, float cellheight, int no_of_col_types)
{
this.no_of_col_types = no_of_col_types;
this.cellheight=cellheight;
this.cellwidth=cellwidth;
grid = new Dictionary<CoordsInt, List<Collidable>>[no_of_col_types];
for (int u = 0; u < no_of_col_types; u++)
grid[u] = new Dictionary<CoordsInt, List<Collidable>>(4000);
}
public abstract void InsertCollidable(Collidable c);
public abstract void InsertCollidable(Grid_AI_Placeable aic);
//gets called in the update loop
public void Clear()
{
for (int u = 0; u < no_of_col_types; u++)
grid[u].Clear();
}
//gets the grid cell of the left down corner
protected void BaseCell(Vector3 v, out int gx, out int gy)
{
gx = (int)((v.X + (WORLDWIDTH / 2)) / cellwidth);
gy = (int)((v.Y + (WORLDHEIGHT / 2)) / cellheight);
}
//gets all cells covered by the AABB
protected void Extent(Vector3 pos, float aabb_width, float aabb_height, out int totalx, out int totaly)
{
var xpos = pos.X + (WORLDWIDTH / 2);
var ypos = pos.Y + (WORLDHEIGHT / 2);
totalx = -(int)((xpos / cellwidth)) + (int)((xpos + aabb_width) / cellwidth) + 1;
totaly = -(int)((ypos / cellheight)) + (int)((ypos + aabb_height) / cellheight) + 1;
}
}
public class GridBaseImpl1 : GridBase{
public GridBaseImpl1(float widthx, float widthy)
: base(widthx, widthy, 3)
{
}
//adds a collidable to the grid /
//caches for intersection test
//checks if it should be tested to prevent penetration /
//tests penetration
//updates close, intersecting, touching lists
//Collidable is an interface for all objects that can be tested geometrically
//the dictionary is indexed by some simple struct that wraps the row and column number in the grid
public override void InsertCollidable(Collidable c)
{
//some tag so that objects don't get checked more than once
Grid_Query_Counter.current++;
//the AABB is allocated in the heap
var aabb = c.CollisionAABB;
if (aabb == null) return;
int gx, gy, totalxcells, totalycells;
BaseCell(aabb.Position, out gx, out gy);
Extent(aabb.Position, aabb.widthx, aabb.widthy, out totalxcells, out totalycells);
//gets which groups to test this object with in an IEnumerable (from a statically created array)
var groupstestedagainst = CollidableCalls.GetListPrevent(c.CollisionType).Select(u => CollidableCalls.group[u]);
var groups_tested_against = groupstestedagainst.Distinct();
var own_group = CollidableCalls.group[c.CollisionType];
foreach (var list in groups_tested_against)
for (int i = -1; i < totalxcells + 1; i++)
for (int j = -1; j < totalycells + 1; j++)
{
var index = new CoordsInt((short)(gx + i), (short)(gy + j));
if (grid[list].ContainsKey(index))
foreach (var other in grid[list][index])
{
if (Grid_Query_Counter.Check(other.Tag))
{
//marks the pair as close, I've tried only keeping the 20 closest but it's still slow
other.Close.Add(c);
c.Close.Add(other);
//caches the pair it so that checking if the pair intersects doesn't go through the grid //structure loop again
c.CachedIntersections.Add(other);
var collision_function_table_id = c.CollisionType * CollidableCalls.size + other.CollisionType;
//gets the function to use on the pair for testing penetration
//the function is in a delegate array statically created to simulate multiple dispatch
//the function decides what coarse test to use until descending to some complete //geometric query
var prevent_delegate = CollidableCalls.preventfunctions[collision_function_table_id];
if (prevent_delegate == null) { Grid_Query_Counter.Put(other.Tag); continue; }
var a = CollidableCalls.preventfunctions[collision_function_table_id](c, other);
//if the query returns true mark as touching
if (a) { c.Contacted.Add(other); other.Contacted.Add(c); }
//marks it as tested in this query
Grid_Query_Counter.Put(other.Tag);
}
}
}
//adds it to the grid if the key doesn't exist it creates the list first
for (int i = -1; i < totalxcells + 1; i++)
for (int j = -1; j < totalycells + 1; j++)
{
var index = new CoordsInt((short)(gx + i), (short)(gy + j));
if (!grid[own_group].ContainsKey(index)) grid[own_group][index] = new List<Collidable>();
grid[own_group][index].Add(c);
}
}
[...]
}
First. Profile your code. Even if you just use manually inserted time stamps to surround blocks you're interested in. I prefer to use the profiler that comes built into Visual Studio Pro.
However, based in your description, I would assume your problems are due to too many draw calls. Once you exceed 200-400 draw calls per frame your performance can drop dramatically. Try batching your rendering and see if this improves performance.
You can use a profiler such as ANTS Profiler to see what may be the problem.
Without any code theres not much I can do.

Categories

Resources