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

fix broken COG cache #289

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package it.geosolutions.imageioimpl.plugins.cog;

import it.geosolutions.imageio.utilities.SoftValueHashMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class ByteChunksMerger {

private ByteChunksMerger(){}

public static Map<Long, byte[]> merge(Map<Long, byte[]> data) {
if (data == null || data.isEmpty()) {
return Collections.emptyMap();
}

// Sort the entries by their keys (byte offsets)
List<Map.Entry<Long, byte[]>> sortedEntries = new ArrayList<>(data.entrySet());
sortedEntries.sort(Map.Entry.comparingByKey());

Map<Long, byte[]> mergedData = new SoftValueHashMap<>(0);
long currentStart = sortedEntries.get(0).getKey();
byte[] currentBytes = sortedEntries.get(0).getValue();

for (int i = 1; i < sortedEntries.size(); i++) {
Map.Entry<Long, byte[]> entry = sortedEntries.get(i);
long nextStart = entry.getKey();
byte[] nextBytes = entry.getValue();

long currentEnd = currentStart + currentBytes.length - 1;
long nextEnd = nextStart + nextBytes.length - 1;

if (nextEnd <= currentEnd){
//included -> skip data
} else if (nextStart <= (currentEnd + 1)) {
// intersection or touching
int overlappingElements = (int)(currentEnd - nextStart + 1);
int combinedLen = currentBytes.length + nextBytes.length - overlappingElements;
byte[] combinedBytes = new byte[combinedLen];
System.arraycopy(currentBytes, 0, combinedBytes, 0, currentBytes.length);
System.arraycopy(nextBytes, overlappingElements, combinedBytes, currentBytes.length, combinedLen - currentBytes.length);
currentBytes = combinedBytes;
} else {
// No overlap, add the current entry to the merged data
mergedData.put(currentStart, currentBytes);
currentStart = nextStart;
currentBytes = nextBytes;
}

}

// Add the last entry
mergedData.put(currentStart, currentBytes);

return mergedData;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package it.geosolutions.imageio.cog;

import it.geosolutions.imageio.utilities.SoftValueHashMap;
import it.geosolutions.imageioimpl.plugins.cog.ByteChunksMerger;
import org.junit.Assert;
import org.junit.Test;

import java.util.Collections;
import java.util.Map;

public class ByteChunksMergerTest {

@Test
public void merge_whenNotConnected_expectNoChange(){
Map<Long, byte[]> orgData = new SoftValueHashMap<>(0);
orgData.put(0L, new byte[] {0,1,2,3});
orgData.put(10L, new byte[] {10,11,12,13});

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(2, mergedData.size());
Assert.assertArrayEquals(new byte[]{0,1,2,3}, mergedData.get(0L));
Assert.assertArrayEquals(new byte[]{10,11,12,13}, mergedData.get(10L));
}

@Test
public void merge_whenIntersecting_expectMerged(){
Map<Long, byte[]> orgData = new SoftValueHashMap<>(0);
orgData.put(0L, new byte[] {0,1,2,3});
orgData.put(3L, new byte[] {3,4,5});

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(1, mergedData.size());
Assert.assertEquals(6, mergedData.get(0L).length);
Assert.assertArrayEquals(new byte[]{0, 1, 2, 3, 4, 5}, mergedData.get(0L));
}

@Test
public void merge_whenTouching_expectMerged(){
Map<Long, byte[]> orgData = new SoftValueHashMap<>(0);
orgData.put(0L, new byte[] {0,1,2,3});
orgData.put(4L, new byte[] {4,5,6,7});

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(1, mergedData.size());
Assert.assertEquals(8, mergedData.get(0L).length);
Assert.assertArrayEquals(new byte[] {0,1,2,3,4,5,6,7}, mergedData.get(0L));
}

@Test
public void merge_whenIncluded_expectMerged(){
Map<Long, byte[]> orgData = new SoftValueHashMap<>(0);
orgData.put(2L, new byte[]{2,3});
orgData.put(0L, new byte[]{0,1,2,3});

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(1, mergedData.size());
Assert.assertArrayEquals(new byte[]{0,1,2,3}, mergedData.get(0L));
}

@Test
public void merge_whenCombinationOfEverything_expectCorrectMerged(){
Map<Long, byte[]> orgData = new SoftValueHashMap<>(0);
orgData.put(0L, new byte[] {0,1,2,3});
orgData.put(2L, new byte[] {2,3,4,5}); // intersecting
orgData.put(1L, new byte[] {1,2}); // included
orgData.put(10L, new byte[] {10,11,12,13}); // new interval
orgData.put(14L, new byte[] {14,15,16,17}); // touching
orgData.put(100L, new byte[] {100,101}); // not connected


Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(3, mergedData.size());
Assert.assertArrayEquals(new byte[] {0,1,2,3,4,5}, mergedData.get(0L));
Assert.assertArrayEquals(new byte[] {10,11,12,13,14,15,16,17}, mergedData.get(10L));
Assert.assertArrayEquals(new byte[] {100,101}, mergedData.get(100L));
}

@Test
public void merge_whenGivenNull_expectEmptyMap(){
Map<Long, byte[]> orgData = null;

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(0, mergedData.size());
}

@Test
public void merge_whenGivenEmpty_expectEmptyMap(){
Map<Long, byte[]> orgData = Collections.emptyMap();

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(0, mergedData.size());
}

@Test
public void merge_whenSingeEntryMap_expectEmptyMap(){
Map<Long, byte[]> orgData = new SoftValueHashMap<>(0);
orgData.put(4L, new byte[]{4,5,6,7});

Map<Long, byte[]> mergedData = ByteChunksMerger.merge(orgData);

Assert.assertEquals(1, mergedData.size());
Assert.assertArrayEquals(new byte[]{4,5,6,7}, mergedData.get(4L));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public void testFetchHeader() throws IOException {

@Test
public void readCogCaching() throws IOException {
DefaultCogImageInputStream cogStream = new DefaultCogImageInputStream(cogUrl2);
CachingCogImageInputStream cogStream = new CachingCogImageInputStream(cogUrl2);
CogImageReader reader = new CogImageReader(new CogImageReaderSpi());
reader.setInput(cogStream);

Expand All @@ -182,4 +182,71 @@ public void readCogCaching() throws IOException {
Assert.assertEquals(height, cogImage.getHeight());
}


@Test
public void readCogWithCaching_whenReusingCachedTiles_expectNoException() throws IOException {
CachingCogImageInputStream cogStream = new CachingCogImageInputStream(cogUrl2);
CogImageReader reader = new CogImageReader(new CogImageReaderSpi());
reader.setInput(cogStream);

CogImageReadParam param = new CogImageReadParam();
param.setRangeReaderClass(HttpRangeReader.class);
param.setHeaderLength(1024);
int x = 128;
int y = 128;
int width = 256;
int height = 256;

param.setSourceRegion(new Rectangle(x, y, width, height));
BufferedImage cogImage1 = reader.read(0, param);

CachingCogImageInputStream cogStream2 = new CachingCogImageInputStream(cogUrl2);
CogImageReader reader2 = new CogImageReader(new CogImageReaderSpi());
reader2.setInput(cogStream2);

CogImageReadParam param2 = new CogImageReadParam();
param2.setRangeReaderClass(HttpRangeReader.class);
param2.setHeaderLength(1024);

param2.setSourceRegion(new Rectangle(x, y, width, height));
BufferedImage cogImage2 = reader2.read(0, param2);

Assert.assertEquals(cogImage1.getWidth(), cogImage2.getWidth());
Assert.assertTrue("code above will fail, but should not",true);
}

@Test
public void readCogWithCaching_whenTilesPartlyCached_expectNoException() throws IOException {
CachingCogImageInputStream cogStream = new CachingCogImageInputStream(cogUrl2);
CogImageReader reader = new CogImageReader(new CogImageReaderSpi());
reader.setInput(cogStream);

CogImageReadParam param = new CogImageReadParam();
param.setRangeReaderClass(HttpRangeReader.class);
param.setHeaderLength(1024);
int x = 128;
int y = 128;
int width = 256;
int height = 256;

param.setSourceRegion(new Rectangle(x, y, width, height));
BufferedImage cogImage1 = reader.read(0, param);

CachingCogImageInputStream cogStream2 = new CachingCogImageInputStream(cogUrl2);
CogImageReader reader2 = new CogImageReader(new CogImageReaderSpi());
reader2.setInput(cogStream2);

CogImageReadParam param2 = new CogImageReadParam();
param2.setRangeReaderClass(HttpRangeReader.class);
param2.setHeaderLength(1024);
int x2 = x + width;
int y2 = y + height;

param2.setSourceRegion(new Rectangle(x2, y2, width, height));
BufferedImage cogImage2 = reader2.read(0, param2);

Assert.assertEquals(cogImage1.getWidth(), cogImage2.getWidth());
Assert.assertTrue("code above will fail, but should not",true);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ public class CacheConfig {
private static String xmlConfigPath;

public CacheConfig() {
useDiskCache = Boolean.getBoolean(getEnvironmentValue(COG_CACHING_USE_DISK, "false"));
useOffHeapCache = Boolean.getBoolean(getEnvironmentValue(COG_CACHING_USE_OFF_HEAP, "false"));
useDiskCache = Boolean.parseBoolean(getEnvironmentValue(COG_CACHING_USE_DISK, "false"));
useOffHeapCache = Boolean.parseBoolean(getEnvironmentValue(COG_CACHING_USE_OFF_HEAP, "false"));
diskCacheSize = Integer.parseInt(
getEnvironmentValue(COG_CACHING_DISK_CACHE_SIZE, Integer.toString(500 * MEBIBYTE_IN_BYTES)));
offHeapSize = Integer.parseInt(
Expand Down
Loading