Skip to content

Commit

Permalink
fix(COG): Use readRGB instead readRasters
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin ETOURNEAU committed Jan 19, 2024
1 parent cb1f661 commit a8457c1
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 134 deletions.
146 changes: 32 additions & 114 deletions examples/js/plugins/COGParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,29 +93,39 @@ function makeWindowFromExtent(source, extent, resolution) {
* @param {THREE.TypedArray | THREE.TypedArray[]} buffers The buffers (one buffer per band)
* @param {number} buffers.width
* @param {number} buffers.height
* @param {number} buffers.byteLength
* @returns {THREE.DataTexture} The generated texture.
*/
function createTexture(source, buffers) {
const { width, height } = buffers;
const { width, height, byteLength } = buffers;
const pixelCount = width * height;
const targetDataType = source.dataType;

// There is no dedicated code in fragment shader to interpret red and green pairs
// So we consider there are 4 channel
const format = THREE.RGBAFormat;
const channelCount = 4;

let texture;
let data;

// Check if it's a RGBA buffer
if (pixelCount * channelCount === byteLength) {
data = buffers;
}

switch (targetDataType) {
case THREE.UnsignedByteType: {
const buf = new Uint8ClampedArray(pixelCount * channelCount);
const data = fillBuffer(buffers, buf, source, source.defaultAlpha);
if (!data) {
// We convert RGB buffer to RGBA
const newBuffers = new Uint8ClampedArray(pixelCount * channelCount);
data = convertToRGBA(buffers, newBuffers, source.defaultAlpha);
}
texture = new THREE.DataTexture(data, width, height, format, THREE.UnsignedByteType);
break;
}
case THREE.FloatType: {
const buf = new Float32Array(pixelCount * channelCount);
const data = fillBuffer(buffers, buf, source, source.defaultAlpha / 255);
if (!data) {
// We convert RGB buffer to RGBA
const newBuffers = new Float32Array(pixelCount * channelCount);
data = convertToRGBA(buffers, newBuffers, source.defaultAlpha / 255);
}
texture = new THREE.DataTexture(data, width, height, format, THREE.FloatType);
break;
}
Expand All @@ -126,113 +136,21 @@ function createTexture(source, buffers) {
return texture;
}

// Important note : a lot of code is duplicated to avoid putting
// conditional branches inside loops, as this can severely reduce performance.
// Note: we don't use Number.isNan(x) in the loops as it slows down the loop due to function
// invocation. Instead, we use x !== x, as a NaN is never equal to itself.
/* eslint no-self-compare: 0 */
function fillBuffer(pixelData, buffers, source, alphaValue) {
// 1 color band
if (pixelData.length === 1) {
const v = pixelData[0];
const length = v.length;
for (let i = 0; i < length; i++) {
const idx = i * 4;
let value;
let a;
const raw = v[i];
if (raw !== raw || raw === source.noData) {
value = source.defaultNoColor;
a = source.defaultNoAlpha;
} else {
value = raw;
a = alphaValue;
}
buffers[idx + 0] = value;
buffers[idx + 1] = value;
buffers[idx + 2] = value;
buffers[idx + 3] = a;
}
}
// 2 band => color + alpha
if (pixelData.length === 2) {
const v = pixelData[0];
const a = pixelData[1];
const length = v.length;
for (let i = 0; i < length; i++) {
const idx = i * 4;
let value;
const raw = v[i];
if (raw !== raw || raw === source.noData) {
value = source.defaultNoColor;
} else {
value = raw;
}
buffers[idx + 0] = value;
buffers[idx + 1] = value;
buffers[idx + 2] = value;
buffers[idx + 3] = a;
}
}
// 3 band => RGB
if (pixelData.length === 3) {
const rChannel = pixelData[0];
const gChannel = pixelData[1];
const bChannel = pixelData[2];
const length = rChannel.length;
for (let i = 0; i < length; i++) {
const idx = i * 4;

let r = rChannel[i];
let g = gChannel[i];
let b = bChannel[i];
let a = alphaValue;

if ((r !== r || r === source.noData)
&& (g !== g || g === source.noData)
&& (b !== b || b === source.noData)) {
r = source.defaultNoColor;
g = source.defaultNoColor;
b = source.defaultNoColor;
a = source.defaultNoColor;
}
function convertToRGBA(buffers, newBuffers, defaultAlpha) {
const { width, height } = buffers;

buffers[idx + 0] = r;
buffers[idx + 1] = g;
buffers[idx + 2] = b;
buffers[idx + 3] = a;
}
for (let i = 0; i < width * height; i++) {
const oldIndex = i * 3;
const index = i * 4;
// Copy RGB from original buffer
newBuffers[index + 0] = buffers[oldIndex + 0]; // R
newBuffers[index + 1] = buffers[oldIndex + 1]; // G
newBuffers[index + 2] = buffers[oldIndex + 2]; // B
// Add alpha to new buffer
newBuffers[index + 3] = defaultAlpha; // A
}
// 4 band => RGBA
if (pixelData.length === 4) {
const rChannel = pixelData[0];
const gChannel = pixelData[1];
const bChannel = pixelData[2];
const aChannel = pixelData[3];
const length = rChannel.length;
for (let i = 0; i < length; i++) {
const idx = i * 4;
let r = rChannel[i];
let g = gChannel[i];
let b = bChannel[i];
let a = aChannel[i];

if ((r !== r || r === source.noData)
&& (g !== g || g === source.noData)
&& (b !== b || b === source.noData)) {
r = source.defaultNoColor;
g = source.defaultNoColor;
b = source.defaultNoColor;
a = source.defaultNoAlpha;
}

buffers[idx + 0] = r;
buffers[idx + 1] = g;
buffers[idx + 2] = b;
buffers[idx + 3] = a;
}
}
return buffers;
return newBuffers;
}

/**
Expand Down Expand Up @@ -285,7 +203,7 @@ const COGParser = (function _() {
window: viewport,
pool: source.pool,
enableAlpha: true,
interleave: false,
interleave: true,
});

const texture = createTexture(source, buffers);
Expand Down
6 changes: 0 additions & 6 deletions examples/js/plugins/COGSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
* @property {number} zoom.max - The maximum level of the source. Default value is Infinity.
* @property {string} url - The URL of the COG.
* @property {GeoTIFF.Pool} pool - Pool use to decode GeoTiff.
* @property {number} noData - Byte used to determine if it is a byte data or not. Default value is 255.
* @property {number} defaultNoColor - Color byte value to apply if it's match {@link noData}. Default value is 255.
* @property {number} defaultNoAlpha - Alpha byte value to apply if it's match {@link noData}. Default value is 0.
* @property {number} defaultAlpha - Alpha byte value used if no alpha is present in COG. Default value is 255.
*
* @example
Expand Down Expand Up @@ -54,16 +51,13 @@ class COGSource extends itowns.Source {
this.fetcher = () => Promise.resolve({});
this.parser = COGParser.parse;

this.defaultNoDataColor = source.defaultNoDataColor || 255;
this.defaultNoDataAlpha = source.defaultNoDataAlpha || 0;
this.defaultAlpha = source.defaultAlpha || 255;

this.whenReady = GeoTIFF.fromUrl(this.url)
.then(async (geotiff) => {
this.geotiff = geotiff;
this.firstImage = await geotiff.getImage();
this.origin = this.firstImage.getOrigin();
this.noData = source.noData ?? this.firstImage.getGDALNoData();
this.dataType = this.selectDataType(this.firstImage.getSampleFormat(), this.firstImage.getBitsPerSample());

this.tileWidth = this.firstImage.getTileWidth();
Expand Down
26 changes: 12 additions & 14 deletions examples/source_file_cog.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@
<div id="description">
<div>Specify the URL of a COG to load:
<input type="text" id="cog_url" />
<button onclick="readCOGURL()">Load</button>
</div>
<div>Specify the CRS of the COG:
<input type="text" id="cog_crs" />
</div>
<button onclick="readCOGURL()">Load</button>
<button onclick="loadSample()">Load sample</button>
<button onclick="loadRGBSample()">Load RGB sample</button>
<button onclick="load1BandSample()">Load 1 band sample</button>
</div>
<div id="viewerDiv"></div>
<script src="js/GUI/GuiTools.js"></script>
Expand All @@ -35,34 +33,34 @@
<script src="js/plugins/COGParser.js"></script>
<script src="js/plugins/COGSource.js"></script>
<script type="text/javascript">
itowns.proj4.defs('EPSG:3947', '+proj=lcc +lat_0=47 +lon_0=3 +lat_1=46.25 +lat_2=47.75 +x_0=1700000 +y_0=6200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs');
itowns.proj4.defs('EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs +type=crs');
itowns.proj4.defs('EPSG:2154', '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs');

var viewerDiv = document.getElementById('viewerDiv');

function readCOGURL() {
var url = document.getElementById('cog_url').value || new URLSearchParams(window.location.search).get('geotiff');
var crs = document.getElementById('cog_crs').value || new URLSearchParams(window.location.search).get('crs');

if (url && crs) {
loadCOG(url, crs);
if (url) {
loadCOG(url);
document.getElementById('cog_url').value = url;
document.getElementById('cog_crs').value = crs;
}
}

function loadSample() {
function loadRGBSample() {
document.getElementById('cog_url').value = 'https://cdn.jsdelivr.net/gh/bloc-in-bloc/iTowns2-sample-data@add-cog-sample/cog/orvault.tif';
document.getElementById('cog_crs').value = 'EPSG:3947';
readCOGURL();
}

function load1BandSample() {
document.getElementById('cog_url').value = 'https://oin-hotosm.s3.amazonaws.com/60fbca155a90f10006fd2fc3/0/60fbca155a90f10006fd2fc4.tif';
readCOGURL();
}

function loadCOG(url, crs) {
// create a source from a Cloud Optimized GeoTiff
var cogSource = new COGSource({
url: url,
crs: crs
crs: "EPSG:2154"
});

cogSource.whenReady.then(() => {
Expand Down

0 comments on commit a8457c1

Please sign in to comment.