Esri GIS setting to the same ViewPoint yields different result - c#

I'm trying to implement some zoom-in-zoom out feature in my program, for this I'm using two ViewPoints with Wgs84 projection, one is default and other one is current, when I return to the default one it seems to "drift" by ~ 0.1 degrees(P.s the logic working as intended)
so on the first time _currentViewPoint ,XMin for example, is 4.77075... which is exactly as _defaultViewPoint XMin
but on the second time(after some panning and zooming the else condition is met and returning to _defaultViewPoint) the _currentViewPoint XMin is 4.62159...
why this so called drift happens?
public void Click()
{
if(_firstClick)
{
_firstClick = false;
_mapService = GetMapService();
_defaultViewPoint = _mapService.IntialViewPoint;
}
_currentViewPoint = _mapService.GetViewPoint();
if(ArePointsEqual(_defaultViewPoint,_currentViewPoint)
{
if(_viewPointToZoom != null)
{
_mapService.SetViewPointAsync(_viewPointToZoom)
}
}
else
{
_viewPointToZoom = _mapService.GetViewPoint();
_mainMapService.SetViewPointAsync(_defaultViewPoint);
}
}
public ViewPoint GetViewPoint()
{
ViewPoint current = null;
try
{
current = EsriGeoView.GetCurrentViewPoint(ViewpointType.BoundingGeomerty);
return ProjectToWgs84(current.TargetGeometry);
}
catch(...)
{
//catch
}
return current;
}
private ViewPoint ProjectToWgs94(Geometry targetGeometry)
{
try
{
var geometry = GeometryEngine.Project(targetGeometry, SpatialReefernces.Wgs84);
return new ViewPoint(geometry);
}
catch(...)
{
//catch
}
}
private async Task SetViewPointAsync(ViewPoint viewpoint)
{
//some non relevent code
await EsriGeoView.SetViewPointAsync(viewpoint);
}
private bool AreViewPointsEqual(Viewpoint vp1, Viewpoint vp2, double tolerance = 1e-4)
{
Envelope r1 = vp1.TargetGeometry.Extent, r2 = vp2.TargetGeometry.Extent;
var x1 = (r1.XMax + r1.XMin) / 2;
var x2 = (r1.YMax + r1.YMin) / 2;
var y1 = (r2.XMax + r2.XMin) / 2;
var y2 = (r2.YMax + r2.YMin) / 2;
return Math.abs(x1-x2) < tolerance && Math.abs(y1-y2) < tolerance);
}

Related

Set destination not working with instantiated childs

I have a script to instantiate a gameobject when the agent reaches its position, when instantiated it will randomly choose any of those 2, then 3, etc... this gameobjects belong to a parent (I use the random with childcount and getChild), however my agent won't move to the instances, just the original one. I've tried using it's position and it's localPosition and none of them works, actually if I use the local position it won't even do the set destination to the original one. I can't guess if its a navmesh problem or if there is an error with my scripting. I can add some images so if anyone can help me.
Script part:
if (plant_gameobject_father.transform.childCount != 0)
{
chosen_child = Random.Range(0, plant_gameobject_father.transform.childCount - 1);
plant_target = plant_gameobject_father.transform.GetChild(chosen_child);
Debug.Log(plant_target.transform.position);
agent_.SetDestination(plant_target.position);
Debug.Log(agent_.pathStatus);
}
Video Sample:
Navmesh working just on the original gameobject
EDIT: When using agent.remainingDistance to check how it's doing it: right after assigning the destination the remaining distance is 0, and when it "arrives" to the target it's remaining distance it's bigger than it should (I have a coroutine using yield return wait until agent.remainingDistance < 3.5f) and still it thinks it has reached destination.
I will upload full script for context understanding(it's a long one)
Could it be that the distances are too big? Mi terrain is larger that 2000 units in lenght.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class insect_IA : MonoBehaviour
{
public ciclo_dia_noche info_horas; //Programado para que haga 3 ciclos de actividad al dia y inactividad de noche.
public GameObject plant_type_small;
public GameObject plant_type_medium;
public GameObject plant_type_big;
Transform plant_target;
GameObject plant_gameobject_father;
public GameObject colmena;
public int objetivo_diario_recoleccion;
public int segundos_recoleccion_polen;
public int segundos_depositado_polen;
public int plant_selector;
public NavMeshAgent agent_;
int selector_accion;
public bool wander_is_happening;
public GameObject colmena_wander_childs;
public Transform wander_target;
public int is_performing_routine_insect;
public bool its_a_new_day;
public birth_controller puedo_reproducir;
public Collider tree_spawner1, tree_spawner2, tree_spawner3, selected_collider;
public Vector3 bounds_max, bounds_min;
public int random_number;
public float spawn_point_x, spawnpoint_z, spawnpoint_y;
public Vector3 spawn_point_tree;
public GameObject tree_big_prefab, tree_med_prefab, tree_peq_prefab;
public int chosen_child;
// Start is called before the first frame update
void Start()
{
}
private void Awake()
{
info_horas = GameObject.Find("Directional_Light").GetComponent<ciclo_dia_noche>();
plant_type_big = GameObject.Find("PLANTAS_GRAND");
plant_type_medium = GameObject.Find("PLANTAS_MED");
plant_type_small = GameObject.Find("PLANTAS_PEQ");
colmena = GameObject.Find("colmena");
puedo_reproducir = GameObject.Find("birth_controlator").GetComponent<birth_controller>();
tree_spawner1 = GameObject.Find("spawn_area1").GetComponent<Collider>();
tree_spawner2 = GameObject.Find("spawn_area2").GetComponent<Collider>();
tree_spawner3 = GameObject.Find("spawn_area3").GetComponent<Collider>();
tree_big_prefab = GameObject.Find("PLANTAS_GRAND/planta_grand");
tree_med_prefab = GameObject.Find("PLANTAS_MED/planta_med");
tree_peq_prefab = GameObject.Find("PLANTAS_PEQ/planta_peq");
agent_ = GetComponent<NavMeshAgent>();
colmena_wander_childs = colmena;
selector_accion = 0;
segundos_recoleccion_polen = 5;
segundos_depositado_polen = 5;
objetivo_diario_recoleccion = 3;
is_performing_routine_insect = 0;
its_a_new_day = true;
random_number = -1;
}
// Update is called once per frame
void Update()
{
if ((puedo_reproducir.plant_big_type.transform.childCount < puedo_reproducir.max_plant_big || puedo_reproducir.plant_med_type.transform.childCount < puedo_reproducir.max_plant_med || puedo_reproducir.plant_peq_type.transform.childCount < puedo_reproducir.max_plant_peq) && info_horas.segundos_globales < info_horas.duracion_dia)
{
insect_state();
}
if (wander_is_happening == false && puedo_reproducir.plant_big_type.transform.childCount > puedo_reproducir.max_plant_big && puedo_reproducir.plant_med_type.transform.childCount > puedo_reproducir.max_plant_med && puedo_reproducir.plant_peq_type.transform.childCount > puedo_reproducir.max_plant_peq)
{
wander_is_happening = true;
is_performing_routine_insect = 4;
StartCoroutine("regular_wander");
}
}
public void insect_state()
{
if (selector_accion == 0)
{
//Debug.Log("buscar padre arbol");
is_performing_routine_insect = 1;
selector_accion = -1;
cojo_un_padre_arbol();
selector_accion = 1;
}
if (selector_accion == 1)
{
//Debug.Log("elegir destino");
selector_accion = -2;
elijo_destino();
selector_accion = 2;
}
if (selector_accion == 2)
{
//Debug.Log("esperar a que llegue");
selector_accion = -3;
check_path();
}
if (selector_accion == 3)
{
//Debug.Log("cogiendo polen");
is_performing_routine_insect = 2;
StartCoroutine("cogiendo_polen");
}
if (selector_accion == 4)
{
//Debug.Log("de vuelta a la colmena");
selector_accion = -5;
volver_colmena();
check_path();
}
if (selector_accion == 5)
{
//Debug.Log("guardo polen");
is_performing_routine_insect = 3;
StartCoroutine("guardando_polen");
}
if (selector_accion == 6)
{
//Debug.Log("reinicio insecto");
is_performing_routine_insect = 4;
StartCoroutine("esperar_proxima_recoleccion");
}
}
public void cojo_un_padre_arbol()
{
if (puedo_reproducir.plant_big_type.transform.childCount < puedo_reproducir.max_plant_big)
{
plant_selector = 2;
}
else if (puedo_reproducir.plant_med_type.transform.childCount < puedo_reproducir.max_plant_med)
{
plant_selector = 1;
}
else if (puedo_reproducir.plant_peq_type.transform.childCount < puedo_reproducir.max_plant_peq)
{
plant_selector = 0;
}
if (plant_selector == 0)
{
plant_gameobject_father = plant_type_small;
}
if (plant_selector == 1)
{
plant_gameobject_father = plant_type_medium;
}
if (plant_selector == 2)
{
plant_gameobject_father = plant_type_big;
}
//Debug.Log("padre elegido:" + plant_gameobject_father);
}
public void elijo_destino()
{
if (plant_gameobject_father.transform.childCount != 0)
{
chosen_child = Random.Range(0, plant_gameobject_father.transform.childCount - 1);
plant_target = plant_gameobject_father.transform.GetChild(chosen_child);
Debug.Log(plant_target.transform.position);
agent_.SetDestination(plant_target.position);
Debug.Log(agent_.pathStatus);
}
else if(plant_gameobject_father.transform.childCount == 0)
{
wander_is_happening = true;
is_performing_routine_insect = 4;
StartCoroutine("regular_wander");
}
//Debug.Log(this.transform.position);
//Debug.Log("planta seleccionada: " + plant_target);
//Debug.Log(agent_.remainingDistance);
}
public void check_path()
{
StartCoroutine("esperar_destino");
}
public void volver_colmena()
{
agent_.SetDestination(colmena.transform.position);
create_plant();
}
public IEnumerator cogiendo_polen()
{
selector_accion = -4;
yield return new WaitForSeconds(segundos_recoleccion_polen);
selector_accion = 4;
}
public IEnumerator guardando_polen()
{
selector_accion = -6;
yield return new WaitForSeconds(segundos_depositado_polen);
selector_accion = 6;
}
public IEnumerator esperar_destino()
{
if (plant_target.tag == "planta_peq")
{
//Debug.Log(agent_.remainingDistance);
yield return new WaitUntil(() => agent_.remainingDistance < 1.7f);
//Debug.Log(agent_.remainingDistance);
agent_.isStopped = true;
agent_.ResetPath();
if (selector_accion == -3)
{
selector_accion = 3;
}
if (selector_accion == -5)
{
selector_accion = 5;
}
}
if (plant_target.tag == "planta_med")
{
//Debug.Log(agent_.remainingDistance);
yield return new WaitUntil(() => agent_.remainingDistance < 3.0f);
//Debug.Log(agent_.remainingDistance);
agent_.isStopped = true;
agent_.ResetPath();
if (selector_accion == -3)
{
selector_accion = 3;
}
if (selector_accion == -5)
{
selector_accion = 5;
}
}
if (plant_target.tag == "planta_grand")
{
Debug.Log(agent_.remainingDistance);
yield return new WaitUntil(() => agent_.remainingDistance < 3.5f);
Debug.Log(agent_.remainingDistance);
agent_.isStopped = true;
agent_.ResetPath();
if (selector_accion == -3)
{
selector_accion = 3;
}
if (selector_accion == -5)
{
selector_accion = 5;
}
}
}
public IEnumerator esperar_proxima_recoleccion()
{
selector_accion = -7;
yield return new WaitForSeconds(1);
selector_accion = 0;
}
public IEnumerator regular_wander()
{
wander_target = colmena_wander_childs.transform.GetChild(Random.Range(0, colmena_wander_childs.transform.childCount - 1));
agent_.SetDestination(wander_target.position);
yield return new WaitUntil(() => agent_.remainingDistance < 0.1f);
agent_.isStopped = true;
agent_.ResetPath();
yield return new WaitForSeconds(5);
wander_is_happening = false;
}
public void create_plant()
{
//Debug.Log("entro a crear una planta");
random_number = Random.Range(0, 3);
if (random_number == 0)
{
selected_collider = tree_spawner1;
}
if (random_number == 1)
{
selected_collider = tree_spawner2;
}
if (random_number == 2)
{
selected_collider = tree_spawner3;
}
bounds_max = selected_collider.bounds.max;
bounds_min = selected_collider.bounds.min;
spawn_point_x = Random.Range(bounds_min.x, bounds_max.x);
spawnpoint_z = Random.Range(bounds_min.z, bounds_max.z);
spawnpoint_y = bounds_max.y;
spawn_point_tree = new Vector3(spawn_point_x, spawnpoint_y, spawnpoint_z);
if (plant_target.tag == "planta_peq")
{
Instantiate(tree_peq_prefab, spawn_point_tree, Quaternion.identity, plant_type_big.transform);
}
if (plant_target.tag == "planta_med")
{
Instantiate(tree_med_prefab, spawn_point_tree, Quaternion.identity, plant_type_medium.transform);
}
if (plant_target.tag == "planta_grand")
{
Instantiate(tree_big_prefab, spawn_point_tree, Quaternion.identity, plant_type_big.transform);
}
Debug.Log(puedo_reproducir.plant_big_type.transform.childCount);
}
}
ยดยดยดยด
There's a lot going on in your script. I'm going to put some calculated guesses here on what might help.
Your birth_controller object seems to have a separate list of all the small/medium/big plants. Are you 100% sure these reference the same as the ones in your Insect_IA object?
You are also using a lot of logic that seems very framerate dependant. Your update function itself calls insect_state() which functions as a state machine that runs through one step per frame, one frame at a time. At the same time, you start coroutines that work alongside that during all that. selector_accion is edited in both insect_state and a lot of those coroutines. Are you sure you're not deadlocking yourself out of certain values of selector_accion which are necessary for the navmesh to work?
Also: Random.Range(int minInclusive, int maxExclusive) is, for the int overload, EXCLUSIVE on the upper bound, so you can remove the -1 from plant_gameobject_father.transform.childCount - 1.
EDIT:
As discussed in the comments and in edits to the original post, the scale of your scene might be causing issues as well. If your navmesh agent cannot physically get as close as your yield WaitUntil statements are waiting for, that's probably deadlocking your logic.

How can I prevent the backgroundWorker causing the UI to become sluggish?

I made a C# WinForms application where I plot thousands of real time data points by using charts. I have noticed that during my application is running when I turn on lets say a web-browser the plot freezes. I tried to plot less points but it seems one never knows which program in parallel will be executed so I'm afraid the CPU usage of other programs depending on the PC will effect the performance.
edit:
private void button1_Click(object sender, EventArgs e)
{
///
_cts = new CancellationTokenSource();
_infiniteLoop = InfiniteLoop(_cts.Token);
}
private async Task InfiniteLoop(CancellationToken cancellationToken = default)
{
ushort[] ushortArray = null;
while (true)
{
Task loopMinimumDurationTask = Task.Delay(100, cancellationToken);
Task<ushort []> calculationTask = Task.Run(() => Calculate());
if (ushortArray != null) PlotData(ushortArray);
ushortArray = await calculationTask;
await loopMinimumDurationTask;
}
}
public ushort [] Calculate()
{
init();
daq.ALoadQueue(chArray, chRange, CHANCOUNT);
ScanOptions options = ScanOptions.Background | ScanOptions.Continuous | ScanOptions.ConvertData;
//setup the acquisiton
UL = daq.AInScan(FIRSTCHANNEL, SINGLE_KANAL_NUM, BUFFERSIZE, ref Rate, Range.Bip10Volts, buffer, options);
UL = daq.GetStatus(out daqStatus, out Count, out Index, FunctionType.AiFunction);
if ((Index >= HALFBUFFSIZE) & ReadLower) //check for 50% more data
{
//get lower half of buffer
UL = MccService.WinBufToArray(buffer, ushortArray, 0, HALFBUFFSIZE);
ReadLower = false; //flag that controls the next read
return ushortArray;
}
else if ((Index < HALFBUFFSIZE) & !ReadLower)
{
//get the upper half
UL = MccService.WinBufToArray(buffer, ushortArray, HALFBUFFSIZE, HALFBUFFSIZE);
ReadLower = true;//flag that controls the next read
return ushortArray;
}
return null;
}
public void PlotData(ushort[] datArray_Plot)
{
////////Thread.Sleep(10);
SerialList1.Clear();
for (int b = 0; b < HALFBUFFSIZE; b++)
{
UL = (daq.ToEngUnits(Range.Bip10Volts, datArray_Plot[b], out temp2));
SerialList1.Add(temp2);
SerialList2.Add(temp2);
ikb_p = ikb_p + 1;
}
int out_size = SerialList1.Count / h; //size of downsampled array
if (out_size <= 2)
out_size = 2;
array = SerialList1.ToArray(); //original array
if (h != 1)
array = Downsample(array, out_size); //downsampled array
if (ikb_p > BUFFERSIZE)
{
chart1.Series["Ch0"].Points.SuspendUpdates();
for (int b = 0; b < out_size; b++)
{
chart1.Series["Ch0"].Points.AddY(array[b]); //Plots each sample or use chart1.Series["Ch0"].Points.DataBindY(array);
if (chart1.Series["Ch0"].Points.Count > display_seconds * FREQ / h)
{
chart1.Series["Ch0"].Points.RemoveAt(0);
}
}
//chart1.Series["Ch0"].Points.ResumeUpdates();
chart1.Invalidate();
}
//FFT
if (SerialList2.Count > 4 * HALFBUFFSIZE / CHANCOUNT)
{
chart2.Series["Freq"].Points.Clear();
float sampling_freq = (float)FREQ;
float[] data = SerialList2.ToArray();
double[] dftIn = new double[data.Length];
double[] dftInIm = new double[data.Length];
double[] DftIn = new double[data.Length];
double[] FFTResult = new double[data.Length];
double[] f = new double[data.Length];
double[] power = new double[data.Length];
double[] window = MathNet.Numerics.Window.Hamming(data.Length);
for (int i = 0; i < data.Length; i++)
{
dftIn[i] = window[i] * (double)data[i];
}
for (int i = 0; i < data.Length; i++)
{
dftInIm[i] = 0.0;
}
FFT(dftIn, dftInIm, out reFFT, out imFFT, (int)Math.Log(data.Length, 2));
for (int i = 0; i < data.Length / 2; i++)
{
if (i > 0)
{
float a = sampling_freq / (float)data.Length;
float x = (float)i * a;
double y = Math.Sqrt(reFFT[i] * reFFT[i] + imFFT[i] * imFFT[i]);
f[i] = x;
FFTResult[i] = 2 * y / (data.Length / 2);
power[i] = 0.5 * FFTResult[i] * FFTResult[i];
}
}
double scale = data.Length / sampling_freq;
chart2.Series["Freq"].Points.DataBindXY(f, power);
float stdCh0 = 0;
float avg1 = SerialList2.Average();
float max1 = SerialList2.Max();
float min1 = SerialList2.Min();
float sum1 = (float)SerialList2.Sum(d => Math.Pow(d - avg1, 2));
stdCh0 = (float)Math.Sqrt((sum1) / (SerialList2.Count() - 1));
label5.Text = avg1.ToString("0.000000");
label22.Text = stdCh0.ToString("0.000000");
label70.Text = max1.ToString("0.000000");
label61.Text = min1.ToString("0.000000");
SerialList2.Clear();
label1.Text = count_sample.ToString();
}
///progressBar1
double ratio = (double)count_sample / (seconds * FREQ);
if (ratio > 1.000)
ratio = 1;
progressBar1.Value = (Convert.ToInt32(1000 * ratio));
progressBar1.Invalidate();
progressBar1.Update();
//Display event handlers
if (comboBox2_changed == true)
{
if (comboBox2.SelectedIndex == 0)
{
//chart1.ChartAreas[0].RecalculateAxesScale();
chart1.ChartAreas[0].AxisY.IsStartedFromZero = false;
}
if (comboBox2.SelectedIndex == 1)
{
//chart1.ChartAreas[0].RecalculateAxesScale();
chart1.ChartAreas[0].AxisY.IsStartedFromZero = true;
}
comboBox2_changed = false;
}
if (comboBox1_changed == true)
{
if (comboBox1.SelectedIndex == 0)
{
chart1.Series["Ch0"].ChartType = SeriesChartType.FastLine;
}
else
chart1.Series["Ch0"].ChartType = SeriesChartType.FastPoint;
}
if (num_updown1_changed)
{
display_seconds = (float)numericUpDown1.Value * 0.001f;
h = (int)numericUpDown2.Value;
chart1.Series["Ch0"].Points.Clear();
//chart1.ChartAreas[0].AxisX.Maximum = display_seconds * FREQ / h;
num_updown1_changed = false;
int avg = (int)((double)FREQ * (Decimal.ToDouble(numericUpDown1.Value) / 1000.0) / max_chart_points);
if (avg != 0)
numericUpDown2.Value = avg;
}
if (num_updown2_changed)
{
display_seconds = (float)numericUpDown1.Value * 0.001f;
h = (int)numericUpDown2.Value;
chart1.Series["Ch0"].Points.Clear();
//chart1.ChartAreas[0].AxisX.Maximum = display_seconds * FREQ / h;
num_updown2_changed = false;
}
}
private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
_cts.Cancel();
// Wait the completion of the loop before closing the form
try { _infiniteLoop.GetAwaiter().GetResult(); }
catch (OperationCanceledException) { } // Ignore this error
}
You could use threadpriority:
Thread.CurrentThread.Priority = ThreadPriority.Highest;
This is however considered to be poor form in most cases since the operating system is in a better position to decide what program deserves CPU time. And it does not need to follow your request for more time, even if you explicitly ask for it.
If plotting takes a considerable amount of time you might consider:
Can you optimize the plotting somehow?
Can you reduce the number of points?
you could perhaps plot a smaller part of the dataset?
You could pre-process the plot to reduce the point density. Screens typically have a resolution of 2k-4k, so if you have a line-chart with more points the user will not be able to see it anyway.
My suggestion is to scrap the obsolete BackgroundWorker, in favor of an infinite asynchronous loop. The example below assumes the existence of a Calculate method that should run on a background thread and should return the result of one calculation, and an UpdateUI method that should run on the UI thread and should consume this result.
private async Task InfiniteLoop(CancellationToken cancellationToken = default)
{
object calculationResult = null;
while (true)
{
Task loopMinimumDurationTask = Task.Delay(100, cancellationToken);
Task<object> calculationTask = Task.Run(() => Calculate());
if (calculationResult != null) UpdateUI(calculationResult);
calculationResult = await calculationTask;
await loopMinimumDurationTask;
}
}
This design has the following characteristics:
The Calculate and the UpdateUI methods are working in parallel.
If the Calculate completes first, it waits the completion of the UpdateUI before starting the next calculation.
If the UpdateUI completes first, it waits the completion of the Calculate before starting the next update of the UI.
If both the Calculate and the UpdateUI complete in under 100 milliseconds, an extra asynchronous delay is imposed, so that no more than 10 loops per second can occur.
The infinite loop can be terminated by canceling the optional CancellationToken.
The object type for the calculationResult variable in the above example is just for demonstration. Unless the result of the calculation is trivial, you should create a class or struct that can store all the data required for updating the UI on every loop. By eliminating all global state you minimize the number of things that can go wrong.
Usage example:
private CancellationTokenSource _cts;
private Task _infiniteLoop;
private void Form_Load(object sender, EventArgs e)
{
_cts = new CancellationTokenSource();
_infiniteLoop = InfiniteLoop(_cts.Token);
}
private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
_cts.Cancel();
// Wait the completion of the loop before closing the form
try { _infiniteLoop.GetAwaiter().GetResult(); }
catch (OperationCanceledException) { } // Ignore this error
}

Reading legacy Word forms checkboxes converted to PDF

Our customers sends us orders as PDF forms which is generated from a Word document built with legacy forms.
Currently people at our customer center is punching the orders into our system, but we have decided to try and automate this task.
I'm able to read the content of the PDF with a simple PdfReader per page:
public static string GetPdfText(string path)
{
var text = string.Empty;
using (var reader = new PdfReader(path))
{
for (var page = 1; page <= reader.NumberOfPages; page++)
{
text += PdfTextExtractor.GetTextFromPage(reader, page);
}
}
return text;
}
But not the checkboxes...
I am able to detect the checkboxes as dictionaries while running through every object in the PDF, but I'm unable to distinguish them from other objects or read the value...
public static IEnumerable<PdfDictionary> ReadCheckboxes(string path)
{
using (var reader = new PdfReader(path))
{
var checkboxes = new List<PdfDictionary>();
for (var i = 0; i < reader.XrefSize; i++)
{
var pdfObject = reader.GetPdfObject(i);
checkboxes.Add((PdfDictionary) pdfObject);
}
return checkboxes;
}
}
What am I missing? I've also tried reading the AcroFields, but they're empty...
I have uploaded a sample PDF with legacy checkboxes here.
Currently there is not option to integrate between our systems or do any changes to the underlying PDF or Word document.
The OP indicated in comments that a solution which returns an output like "checkbox at position x0, y0, checked; checkbox at position x1, y1, not checked; ..." would suffice, i.e. his "forms" are static enough so that these positions allow identification of the meaning of the respective checkboxes. Thus, here an implementation of this variant.
I just saw that the question is tagged c# while I have implemented the search using Java. This should not be too big a problem, the code should be easy to port. If there are problems porting, I'll add a C# version here.
As the checkboxes are drawn using vector graphics, the text extraction already used by the OP does not find them. Fortunately, though, the iText parsing framework can also be used to look for vector graphics.
Thus, we first need an ExtRenderListener (IExtRenderListener in iTextSharp) which collects the boxes. It only has non-trivial implementations of the interface methods modifyPath and renderPath:
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
switch (renderInfo.getOperation())
{
case PathConstructionRenderInfo.RECT:
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
float w = renderInfo.getSegmentData().get(2);
float h = renderInfo.getSegmentData().get(3);
rectangle = new Rectangle(x, y, x+w, y+h);
}
case PathConstructionRenderInfo.MOVETO:
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
moveToVector = new Vector(x, y, 1);
lineToVector = null;
break;
}
case PathConstructionRenderInfo.LINETO:
{
if (moveToVector != null)
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
lineToVector = new Vector(x, y, 1);
}
break;
}
default:
moveToVector = null;
lineToVector = null;
}
}
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
if (rectangle != null)
{
Vector a = new Vector(rectangle.getLeft(), rectangle.getBottom(), 1).cross(renderInfo.getCtm());
Vector b = new Vector(rectangle.getRight(), rectangle.getBottom(), 1).cross(renderInfo.getCtm());
Vector c = new Vector(rectangle.getRight(), rectangle.getTop(), 1).cross(renderInfo.getCtm());
Vector d = new Vector(rectangle.getLeft(), rectangle.getTop(), 1).cross(renderInfo.getCtm());
Box box = new Box(new LineSegment(a, c), new LineSegment(b, d));
boxes.add(box);
}
if (moveToVector != null && lineToVector != null)
{
if (!boxes.isEmpty())
{
Vector from = moveToVector.cross(renderInfo.getCtm());
Vector to = lineToVector.cross(renderInfo.getCtm());
boxes.get(boxes.size() - 1).selectDiagonal(new LineSegment(from, to));
}
}
}
moveToVector = null;
lineToVector = null;
rectangle = null;
return null;
}
Vector moveToVector = null;
Vector lineToVector = null;
Rectangle rectangle = null;
public Iterable<Box> getBoxes()
{
return boxes;
}
final List<Box> boxes = new ArrayList<Box>();
(from CheckBoxExtractionStrategy.java)
It uses a helper class Box which models the checkboxes using their respective diagonals:
public class Box
{
public LineSegment getDiagonal()
{
return diagonalA;
}
public boolean isChecked()
{
return selectedA && selectedB;
}
Box(LineSegment diagonalA, LineSegment diagonalB)
{
this.diagonalA = diagonalA;
this.diagonalB = diagonalB;
}
void selectDiagonal(LineSegment diagonal)
{
if (approximatelyEquals(diagonal, diagonalA))
selectedA = true;
else if (approximatelyEquals(diagonal, diagonalB))
selectedB = true;
}
boolean approximatelyEquals(LineSegment a, LineSegment b)
{
float permissiveness = a.getLength() / 10.0f;
if (approximatelyEquals(a.getStartPoint(), b.getStartPoint(), permissiveness) &&
approximatelyEquals(a.getEndPoint(), b.getEndPoint(), permissiveness))
return true;
if (approximatelyEquals(a.getStartPoint(), b.getEndPoint(), permissiveness) &&
approximatelyEquals(a.getEndPoint(), b.getStartPoint(), permissiveness))
return true;
return false;
}
boolean approximatelyEquals(Vector a, Vector b, float permissiveness)
{
return a.subtract(b).length() < permissiveness;
}
boolean selectedA = false;
boolean selectedB = false;
final LineSegment diagonalA, diagonalB;
}
(Inner class in CheckBoxExtractionStrategy.java)
Applying it like this to the sample document:
for (int page = 1; page <= pdfReader.getNumberOfPages(); page++)
{
System.out.printf("\nPage %s\n====\n", page);
CheckBoxExtractionStrategy strategy = new CheckBoxExtractionStrategy();
PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);
parser.processContent(page, strategy);
for (Box box : strategy.getBoxes())
{
Vector basePoint = box.getDiagonal().getStartPoint();
System.out.printf("at %s, %s - %s\n", basePoint.get(Vector.I1), basePoint.get(Vector.I2),
box.isChecked() ? "checked" : "unchecked");
}
}
one gets the output
Page 1
====
at 73.104, 757.8 - checked
at 86.544, 757.8 - checked
at 99.984, 757.8 - unchecked
for the OP's document

Make variable method

I am currently working on a XP Leveling system in unity. At the moment my code works (this first part is inside the onclick method):
currentExp = Xp.LevelToXP(combat + 1);
if (combatExp + 5 < currentExp)
{
combatExp += 5;
if (previousExp == 0)
{
float fill = (float)(currentExp) / 100;
XpBar.fillAmount += (5 / fill) / 100;
}
else
{
float fill = (float)(currentExp - previousExp) / 100;
XpBar.fillAmount += (5 / fill) / 100;
}
}
else if (combatExp + 5 == currentExp)
{
combatExp += 5;
combat++;
previousExp = currentExp;
XpBar.fillAmount = 0;
}
else if (combatExp + 5 > currentExp)
{
combatExp += 5;
combat++;
previousExp = currentExp;
XpBar.fillAmount = 0;
float remainingExp = (float)combatExp - currentExp;
XpBar.fillAmount += (remainingExp / currentExp) / 100f;
}
txtCombatLvl.text = "Combat Level: " + combat;
this is what happens when you press the button.
but when i try to put all of this in a method instead of the onclick method
void AddExp (int skill, int skillExp, int expAmount)
{
currentExp = Xp.LevelToXP(skill + 1);
if (skillExp + expAmount < currentExp)
{
skillExp += expAmount;
if (previousExp == 0)
{
float fill = (float)(currentExp) / 100;
XpBar.fillAmount += (expAmount / fill) / 100;
}
else
{
float fill = (float)(currentExp - previousExp) / 100;
XpBar.fillAmount += (expAmount / fill) / 100;
}
}
else if (skillExp + expAmount == currentExp)
{
skillExp += expAmount;
skill++;
previousExp = currentExp;
XpBar.fillAmount = 0;
}
else if (skillExp + expAmount > currentExp)
{
skillExp += expAmount;
skill++;
previousExp = currentExp;
XpBar.fillAmount = 0;
float remainingExp = (float)skillExp - currentExp;
XpBar.fillAmount += (remainingExp / currentExp) / 100f;
}
}
And I try to access it by calling it like this:
AddExp(combat, combatExp, 5);
none of my combat xp and level don't save. Can someone point me in the right direction since none of what i tried worked.
The parameters of a C# method are passed by value and not by references, so changes to them inside a function are local to the function. If you want the values updated then you prefix them with the ref keyword.
void AddExp (ref int skill, ref int skillExp, int expAmount)
Alternate Solution
Alternatively, you could simplify your design with an encapsulation of your concept of a Skill with a class. Here's a possibility, I couldn't quite follow your math and am not familiar with your other classes, so you can adjust this as necessary.
public class Skill
{
private string _name;
private int _currentValue;
private int _currentLevel = 1;
public Skill(string name) {
}
public string Name { get { return _name; } }
public int CurrentXp { get { return _currentValue; } }
public int CurrentLevel { get { return _currentLevel; } }
public int XpRequiredForNextLevel { get { return Xp.LevelToXP(_currentLevel + 1); } }
public int XpRequiredForCurrentLevel { get { return Xp.LevelToXP(_currentLevel); } }
private float CalculateFillPercentage(int xp)
{
var xpInLevel = XpRequiredForNextLevel - XpRequiredForCurrentLevel;
return (float)(xp - XpRequiredForCurrentLevel) / (float)xpInLevel;
}
public void AddXp(int expAmount)
{
_currentValue += expAmount;
int XpInCurrentLevel = XpRequiredForNextLevel - XpRequiredForCurrentLevel;
if (_currentValue > XpRequiredForNextLevel) {
} else {
_currentLevel++;
}
XpBar.fillAmount = CalculateFillPercentage (_currentValue);
}
}
You could use this by holding instances of each skill, for example:
Skill combat = new Skill("combat");
And then later
combat.AddXp(50);
Each skill would be self contained. I'm not sure about the XpBar or how that is controlled between different types of skills, but you could further encapsulate by calculating internally, so the skill provides everything your UI needs to keep up to date.

Invoke doesn't update GUI appropriately

I wrote this for a meteorology class. For some reason, the textboxes aren't updating on the GUI correctly for large numbers (large numbers of photons). The calculation completes, but the textboxes don't update.
I suspect the problem is with calling Invoke(), but I can't for the life of me see what's going wrong. I've tried using both Invoke() and BeginInvoke() with similar results.
Can anyone help figure out where I'm going wrong?
Thanks!
PS> Please forgive the global variables. Was planning on cleaning them up later...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace CloudTransmittance
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonCalculate_Click(object sender, EventArgs e)
{
Thread t = new Thread(calculateModel);
t.Start();
}
//number of photons that have gone to albedo, direct, or diffuse transmittance
private ulong top = 0;
private ulong direct = 0;
private ulong diffuse = 0;
private ulong absorbed = 0;
private ulong failed = 0;
private ulong photons = 0;
private void calculateModel()
{
//model variables
double theta = 0;
double tauStar = 0;
double omega = 0;
double g = 0;
photons = 0;
//Get data from form
theta = Convert.ToDouble(textBoxTheta.Text);
tauStar = Convert.ToDouble(textBoxTau.Text);
omega = Convert.ToDouble(textBoxOmega.Text);
g = Convert.ToDouble(textBoxG.Text);
photons = Convert.ToUInt64(textBoxPhotons.Text);
//Clear the progress bar and set its limits
this.progressBar1.BeginInvoke(
(MethodInvoker)delegate()
{
this.progressBar1.Minimum = 0;
this.progressBar1.Value = 0;
this.progressBar1.Maximum = (int)photons;
this.progressBar1.Step = 1;
});
//Clear the text boxes
this.textBoxAlbedo.Invoke(
(MethodInvoker)delegate()
{
this.textBoxAlbedo.Text = "";
});
this.textBoxDirect.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDirect.Text = "";
});
this.textBoxDiffuse.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDiffuse.Text = "";
});
this.textBox1.Invoke(
(MethodInvoker)delegate()
{
this.textBox1.Text = "";
});
this.textBox2.Invoke(
(MethodInvoker)delegate()
{
this.textBox2.Text = "";
});
//convert theta to radians from degrees
theta *= Math.PI / 180;
//number of photons that have gone to albedo, direct, or diffuse transmittance
top = 0;
direct = 0;
diffuse = 0;
absorbed = 0;
failed = 0;
//Random number generator
Random r = new Random();
double randomValue = 0;
int count = 1000; //number of iterations of the problem...
double delta = 0.00001; //close enough to "1" for calculations, since C# random goes from [0, 1) instead of [0, 1]
//Calculate transmittance
for (ulong photonCount = 0; photonCount < photons; photonCount++)
{
bool scattered = false;
double newTheta = theta; //needed for looping
int i = 0; //counting variable used to prevent infinite looping
for (i = 0; i < count; i++)
{
double length = calculateTauP(); //length of the photon's travel
double newTau = calculateTau(newTheta, length);
if (newTau < 0)
{
top++; //photon has exited through the top
break; //move to the next photon
}
else if (newTau > tauStar)
{
//exited through the bottom of the cloud
if (scattered == false)
{
//direct transmittance
direct++;
}
else
{
//diffuse transmittance
diffuse++;
}
break;
}
else
{
//photon is either scattered or absorbed
randomValue = r.NextDouble();
if (randomValue >= omega) // || ((omega == 1) && (randomValue >= (omega - delta)) )
{
//photon absorbed, no longer of interest
absorbed++;
break;
}
else
{
//photon scattered, determine direction
scattered = true;
newTheta = calculateNewAngle(newTau, newTheta, g, randomValue);
}
}
}
if (i >= count)
{
failed++;
}
this.progressBar1.BeginInvoke(
(MethodInvoker)delegate()
{
this.progressBar1.PerformStep();
});
}
//Update Form values
displayData();
}
private void displayData()
{
if (this.textBoxAlbedo.InvokeRequired)
{
this.textBoxAlbedo.Invoke(
(MethodInvoker)delegate()
{
this.textBoxAlbedo.Text = ((double)top / (double)photons).ToString();
});
}
else
{
textBoxAlbedo.Text = ((double)top / (double)photons).ToString();
}
if (this.textBoxDirect.InvokeRequired)
{
this.textBoxDirect.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDirect.Text = ((double)direct / (double)photons).ToString();
});
}
else
{
textBoxDirect.Text = ((double)direct / (double)photons).ToString();
}
if (this.textBoxDiffuse.InvokeRequired)
{
this.textBoxDiffuse.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDiffuse.Text = ((double)diffuse / (double)photons).ToString();
});
}
else
{
textBoxDiffuse.Text = ((double)diffuse / (double)photons).ToString();
}
if (this.textBox1.InvokeRequired)
{
this.textBox1.Invoke(
(MethodInvoker)delegate()
{
this.textBox1.Text = absorbed.ToString();
});
}
else
{
textBox1.Text = absorbed.ToString();
}
if (this.textBox2.InvokeRequired)
{
this.textBox2.Invoke(
(MethodInvoker)delegate()
{
this.textBox2.Text = failed.ToString();
});
}
else
{
textBox2.Text = failed.ToString();
}
}
private double calculateNewAngle(double length, double angle, double g, double randomNumber)
{
double newAngle = 0;
double cos = (1 / (2 * g)) * (1 + Math.Pow(g, 2) - Math.Pow(((1 - Math.Pow(g, 2)) / (1 + g * (2 * randomNumber - 1))), 2));
newAngle += angle + cos;
while (newAngle >= 2 * Math.PI)
{
newAngle -= 2 * Math.PI; //normalize the angle to 0 <= angle < 2PI
}
return newAngle;
}
private double calculateTauP()
{
Random r = new Random();
double distance = -1 * Math.Log(1 - r.NextDouble());
return distance;
}
private double calculateTau(double angle, double tauP)
{
double tau = tauP * Math.Cos(Math.PI/2 - angle);
return tau;
}
}
}
Stop using Invoke and BeginInvoke to update the UI. Despite what you may have been told it is not that great of solution. Actually, in situations like these where all you want to to do is update the UI with progress information it is probably the worst solution. Instead, have your worker thread publish its progress information to an immutable data structure that can be shared with the UI thread. Then have your UI thread poll for it on a reasonable interval using a System.Windows.Forms.Timer.
public class YourForm : Form
{
private class ProgressInfo
{
public ProgressInfo(ulong top, ulong direct, ulong diffuse, ulong absorbed, ulong failed, ulong photons)
{
// Set properties here.
}
public ulong Top { get; private set; }
public ulong Direct { get; private set; }
public ulong Diffuse { get; private set; }
public ulong Dbsorbed { get; private set; }
public ulong Failed { get; private set; }
public ulong Photons { get; private set; }
}
private volatile ProgressInfo progress = null;
private void calculateModel()
{
for (ulong photonCount = 0; photonCount < photons; photonCount++)
{
// Do your calculations here.
// Publish new progress information.
progress = new ProgressInfo(/* ... */);
}
}
private void UpdateTimer_Tick(object sender, EventArgs args)
{
// Get a local reference to the data structure.
// This is all that is needed since ProgressInfo is immutable
// and the member was marked as volatile.
ProgressInfo local = progress;
this.textBoxAlbedo.Text = ((double)local.Top / (double)local.Photons).ToString();
this.textBoxDirect.Text = ((double)local.Direct / (double)local.Photons).ToString();
this.textBoxDiffuse.Text = ((double)local.Diffuse / (double)local.Photons).ToString();
this.textBox1.Text = local.Absorbed.ToString();
this.textBox2.Text = local.Failed.ToString();
}
Notice several things here.
The code is a lot easier to understand and follow.
The UI thread gets to decide when and how often its controls should be updated.
You get more throughput on the worker thread since it does not have to wait for a response from the UI as would occur with Invoke.
I rip on these Invoke and BeginInvoke solutions a lot because in many cases they are terrible solutions. Using a BackgroundWorker is a little better, but it still forces you into the push method of updating the UI (via the same marshaling techniques behind the scenes nonetheless). The pull method can be (and often is) a more elegant solution and usually more efficient.

Categories