Skip to content

fixes all plot types reprojections#691

Merged
lazarusA merged 17 commits into
mainfrom
la/fix_reprojections
Jul 19, 2026
Merged

fixes all plot types reprojections#691
lazarusA merged 17 commits into
mainfrom
la/fix_reprojections

Conversation

@lazarusA

@lazarusA lazarusA commented Jul 18, 2026

Copy link
Copy Markdown
Member

closes #690

Our test case goes from

+proj=lcc +lat_1=38.5 +lat_2=38.5 +lat_0=38.5 +lon_0=-97.5 +x_0=0 +y_0=0 +R=6371229 +units=m +no_defs

to

+proj=longlat +R=6371229 +no_defs +units=degrees

A lot of the changes to other files were done to not rely on hard coded numbers and use a more generic approach to determine values. Plus some changes that were not done/updated when doing #674, yeah, mostly things missed in that PR. Plus, support for point-cloud and re-projections into the sphere plot type.

Also, analytics indeed doesn't work on reprojected data. We should address that in a different PR.

Some screenshots.

Sphere

Screenshot 2026-07-18 at 22 49 41

FlatMap

Screenshot 2026-07-18 at 22 48 25

Point clouds

Screenshot 2026-07-18 at 22 48 03

Volume

Screenshot 2026-07-18 at 22 47 06

Blocks

Screenshot 2026-07-18 at 22 51 22

@lazarusA
lazarusA requested a review from TheJeran July 18, 2026 21:06

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces reprojection support across multiple visualization components (including PointCloud, Sphere, SphereBlocks, and CountryBorders) by integrating a remap texture and handling coordinate flipping. The review feedback highlights several critical issues, such as potential runtime crashes and division-by-zero errors when coordinate ranges or shape dimensions are zero. Additionally, it points out a Three.js-specific issue where setting a define to false still defines the macro in GLSL, recommending that these defines be conditionally omitted or deleted instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/components/plots/PointCloud.tsx Outdated
Comment thread src/components/plots/PointCloud.tsx Outdated
Comment thread src/components/plots/PointCloud.tsx
Comment thread src/components/plots/PointCloud.tsx Outdated
Comment thread src/components/plots/Sphere.tsx
Comment thread src/components/plots/CountryBorders.tsx Outdated
Comment thread src/components/plots/CountryBorders.tsx Outdated
Comment thread src/components/textures/ProjectionTexture.ts Outdated
Comment thread src/components/textures/shaders/pointVertex.glsl Outdated
Comment thread src/components/textures/shaders/pointVertex.glsl Outdated
@lazarusA

Copy link
Copy Markdown
Member Author

Done with all suggestions 👍🏻 !

@TheJeran

Copy link
Copy Markdown
Collaborator

Do you have the test cases datasets remote somewhere?

Also, no issues at the moment. But we will have to change the logic for the sphere. Because it doesn't always make sense to show the reprojected data.

image

Also feels like the point-cloud could be simplified somehow. But since reprojection is basically brand new data. I think what you have done is at the moment the best option I can think of. This also would help free up some data if we just decided to stick with sampling the main texture array for the non-reprojected data as well. Can remove the data textureData global.

I just got two things I will comment in the code

@lazarusA

Copy link
Copy Markdown
Member Author

See issue #690 for the issue and example data.

Also, I do like the deformed reprojection on the sphere.

Comment thread src/components/plots/PointCloud.tsx Outdated
defines: {
REPROJECT: remapTexture ? true : false,
FLIP_Y: flipY ? true : false,
IS_2D: is2D,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this, (and the subsequent shader counterpart) redundant? I don't think ou can have pointcloud with 2D data.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on 2d point cloud data, we should support that. So, I will come back to that and enable it.

newAxisDimArrays[yIdx] = yTicks;

const newAxisDimUnits = [...axisDimUnits];
const targetUnits = (crsCheck.oProj as any)?.units || 'degrees';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. It can't default to degrees

Image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but it needs something empty then. Because otherwise I think it will failed for some cases. Empty should be fine, ''.

Comment on lines +197 to +251
if (plotType === 'point-cloud') {
targetWidth = width;
targetHeight = height;
xTicks = linspace(minX, maxX, targetWidth);
yTicks = flipY ? linspace(maxY, minY, targetHeight) : linspace(minY, maxY, targetHeight);
data = new Uint16Array(targetWidth * targetHeight * 4);

const xDiff = Math.abs(maxX - minX);
const yDiff = Math.abs(maxY - minY);

for (let j = 0; j < targetHeight; j++) {
const lat = yArray[j];
for (let i = 0; i < targetWidth; i++) {
const lon = xArray[i];
const [px, py] = proj.forward([lon, lat]);
const valid = (isFinite(px) && isFinite(py)) ? 1 : 0;

const u = xDiff > 0 ? (px - minX) / xDiff : 0;
const v = yDiff > 0 ? (py - minY) / yDiff : 0;

const idx = (j * targetWidth + i) * 4;
data[idx] = THREE.DataUtils.toHalfFloat(u);
data[idx + 1] = THREE.DataUtils.toHalfFloat(v);
data[idx + 2] = THREE.DataUtils.toHalfFloat(valid);
}
}
} else {
targetWidth = Math.ceil(adjustedResolution * aspectRatio);
targetHeight = adjustedResolution;
xTicks = linspace(minX, maxX, targetWidth);
yTicks = flipY ? linspace(maxY, minY, targetHeight) : linspace(minY, maxY, targetHeight);

// Detect if coordinate axes are descending
const isXDescending = xArray.length > 1 ? xArray[0] > xArray[xArray.length - 1] : false;
const isYDescending = yArray.length > 1 ? yArray[0] > yArray[yArray.length - 1] : false;

data = new Uint16Array(targetWidth * targetHeight * 4);
const xRangeDiff = xMax - xMin;
const yRangeDiff = yMax - yMin;
for (let j = 0; j < targetHeight; j++) {
for (let i = 0; i < targetWidth; i++) {
const [lon, lat, valid] = safeInverse(proj, [xTicks[i], yTicks[j]]);
const u = xRangeDiff > 0 ? (isXDescending ? (xMax - lon) / xRangeDiff : (lon - xMin) / xRangeDiff) : 0;
const v = yRangeDiff > 0 ? (isYDescending ? (yMax - lat) / yRangeDiff : (lat - yMin) / yRangeDiff) : 0;

// Check boundary bounds to avoid displaying clamped blocks outside the dataset area
const inBounds = lon >= xMin && lon <= xMax && lat >= yMin && lat <= yMax;
const validVal = (valid === 1 && inBounds) ? 1 : 0;

const idx = (j * targetWidth + i) * 4;
data[idx] = THREE.DataUtils.toHalfFloat(u);
data[idx + 1] = THREE.DataUtils.toHalfFloat(v);
data[idx + 2] = THREE.DataUtils.toHalfFloat(validVal);
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the use cases where this is necessary? When does the normal case not work? I'm not convinced this isn't redundant or can't just be trivially undid by flipping the resulting texture.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first part is to do reprojection directly on point data, second on your resampling texture strategy. Or? what else do you mean? Also, keeping track of flip or not flip is necessary.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the point-cloud doesn't increase in resolution?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

point-cloud should do native points. No need for resolution. What we see is what we get.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... okay. I think I will come back to this. Something in my brain says there's some way to simplify it but I'm to tired to discover if thats true. But otherwise I'm content to merge.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's open an issue to keep track of this then.

This reverts commit c07b0c4.
@lazarusA

Copy link
Copy Markdown
Member Author

once you are done with your changes and satisfied let me know so that I can pull and test before merging. Thanks for taking a look so fast.

@lazarusA

lazarusA commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

Something went south. I will track down what was it, and revert the minimal needed lines.

Edit:
Sorry, it only works reliable if we splat things. I will go back to those.

Screenshot 2026-07-19 at 13 48 43 Screenshot 2026-07-19 at 13 48 34 Screenshot 2026-07-19 at 13 48 23 Screenshot 2026-07-19 at 13 48 13

@lazarusA

Copy link
Copy Markdown
Member Author

fallback to degrees allows for
Screenshot 2026-07-19 at 14 38 44

instead of

Screenshot 2026-07-19 at 14 40 31

I agree that this does not apply to all projections, but if not information is provided about the dest projection units having a default seems like the reasonable thing to do. In any case, we need a proper helper function that infers the right units from the +proj string entry. Will open an issue and independent PR to fix that. #693

@TheJeran

Copy link
Copy Markdown
Collaborator

The degrees thing is on the user to properly add the degrees in the proj string. +units=degrees .

But since all other units are massive. You can just add a check that if there are no units it checks if the new axis fits inside the lat/lon domains. if the max/min values are larger than 360 or less than -180 its almost certainly not going to be degrees. unless its a tiny square with lon/lat offsets.

I also thing lon/lat projections have a name in the proj object indicating that it will be degrees.

Comment on lines +63 to +70
const oldTextures = textures;
setTimeout(() => {
oldTextures.forEach((tex) => {
tex.dispose();
if (tex.source) (tex.source as any).data = null;
});
}, 0);
setTextures(null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you reproduce this?

I really don't like adding extra code unless there's a specific reproducible error and I haven't had any issues with this since it's implementation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I can. I did test it. Without it, I will always see a red type error on console, I don't like those. This does work.

…ojectionTexture, user is responsible for it.
@lazarusA
lazarusA merged commit f6e2602 into main Jul 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reprojection issues

2 participants