Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Null values in grid #49

Closed
kpetrow opened this issue Jul 23, 2020 · 9 comments · Fixed by #61
Closed

Handle Null values in grid #49

kpetrow opened this issue Jul 23, 2020 · 9 comments · Fixed by #61
Labels
enhancement New feature or request

Comments

@kpetrow
Copy link

kpetrow commented Jul 23, 2020

I have grids with null values. Countour does not like null or undefined in the array of values. Any solutions? I can set the nulls to gridMin-1 and then set the threshold but i still Get steps down from the Larger than min values to gridMin.

@Fil
Copy link
Member

Fil commented Jul 29, 2020

I don't think there is a unique answer (or specification).

If null corresponds to missing data, maybe it should be ignored rather than set to the min? In that case we could to set nulls to an average of the neighbors… but then what happens when all neighbors are null too.

Also note that the current algorithm currently “works” with null (considered as 0, it creates a hole), but breaks with undefined or NaN, sometimes resulting in empty contours.

I have a test notebook here https://observablehq.com/d/c490a61e8eef537e (not very advanced).

@Fil Fil added the enhancement New feature or request label Jul 29, 2020
@kpetrow
Copy link
Author

kpetrow commented Jul 29, 2020

@Fil thanks for the response. I am not able to reproduce the donuts like you are seeing. I thought at first it might be because i cant pass in "null". I am using a float32array this only allows for undefined. but i converted my float32array to a regular array and swapped out all the undefined for nulls. It gives me a rapid descent in the donuts down to the min range


                for(var i = min; i<max; i+= step){
			range.push(i);
		}
		var temp = Array.from(tempGrid.elevations);

		for(var i = 0;i<temp.length;i++){
			if( isNaN(temp[i]) )
				temp[i] = null ;
		};

		var myCountours = d3.contours()
		.size([tempGrid.metaData.nCol+1, tempGrid.metaData.nRow+1])
		.thresholds(range)
		(temp);

resulting data set. with surface model
image

EDIT: after rereading your post a couple times, i think you see what i am saying. Solution would be to have it skip the node completely? unknown is unknown do nothing

@Fil
Copy link
Member

Fil commented Jul 29, 2020

Yes—but "do nothing" is not an option, since the marching squares algorithm needs to know which way to use if it has to cross here for value=0 in this 3x1 rectangle:

-1 N/A 1

that's why I suggest doing a first pass to replace all nulls (or undefined/NaN) by neighboring values (or mean of neighboring values), if it works in this use case.

I don't think this would be in the scope of d3-contour though because 1) it's costly 2) the situation can become pretty difficult (recursive) if you have many N/A values—or even only N/A 3) most importantly, the correct solution really depends on what NaNs mean in the data model.

If NaN means for instance "sea vs land", then a value of -1 (or gridMin-1) might be the correct approach. If NaN means "missing data", then some kind of local interpolation might be better.

@Fil
Copy link
Member

Fil commented Jul 29, 2020

Rather than breaking silently, throw an error?

@kpetrow
Copy link
Author

kpetrow commented Jul 29, 2020

if not feasible i understand and will have to come up with a post process solution( could the lines be stacked vertically so i eliminate ones that have identical XY and are less than maxZ for xy combo) or something else. We are forming surface models from gps movements; so unknown means just that unknown.

If NaN means "missing data", then some kind of local interpolation might be better.
image

Sometimes its a cliff, sometimes its a gradual slope, we just cant tell what it is(its usually benches with a road).

Could you suggest an alternative method/algorithm that might work?

Rather than breaking silently, throw an error?

Yeah that might be a better solution. I still need to iterate through the flot32array to replace undefined with minRange - 1. I think throwing an error on undefined might be very helpful because it breaks the entire contour line and is confusing.

Thanks for the awesome lib.

@Fil
Copy link
Member

Fil commented Jul 30, 2020

If you're working with non-gridded data you might want to take a look at https://github.com/Fil/d3-tricontour

@kpetrow
Copy link
Author

kpetrow commented Jul 30, 2020

The tri-contour library does really take triangles. It takes points and Delaunay's up triangles. That wont work either because it TINs up my holes. I could make a tin by just splitting up my grid nodes, but tricontour doesn't seem to take triangles. Still searching for a solution. looks like some post process pound and ground for similar lines might be best at this point.

@kpetrow
Copy link
Author

kpetrow commented Jul 31, 2020

Just to help the next person... I decided to go with a solution of post processing. If line vertex not on the grid then toss it. and make a new segment. Bunch of little things to deal with but pretty much:

                       (
                            isNaN(tempGrid.elevations[array_pos]) ||
                            isNaN(tempGrid.elevations[array_pos - 1]) ||
                            isNaN(tempGrid.elevations[array_pos + tempGrid.metaData.nCol + 1]) ||
                            isNaN(tempGrid.elevations[array_pos - (tempGrid.metaData.nCol + 1)]) ||
                            isNaN(tempGrid.elevations[array_pos + 1])
                        ) &&
                        (
                            tempGrid.elevations[array_pos] - 1 > myCountours[i].value
                        )

Thanks for the help. Great lib

image

@mbostock
Copy link
Member

mbostock commented Jul 3, 2022

We should treat null/undefined/NaN as -Infinity, i.e., a value that is lower than any possible threshold and never included in any contour.

Fil added a commit that referenced this issue Jul 7, 2022
@Fil Fil closed this as completed in #61 Jan 8, 2023
Fil added a commit that referenced this issue Jan 8, 2023
* null, undefined, NaN and -Infinity create holes. Ignore infinite values when computing the thresholds.
closes #49

* Update src/contours.js

Co-authored-by: Mike Bostock <mbostock@gmail.com>

* post-review

* revert unrelated style changes

* nullish

* extract closure

* a few comments

* another comment

* another comment

Co-authored-by: Mike Bostock <mbostock@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Development

Successfully merging a pull request may close this issue.

3 participants