diff options
| author | Bananeweizen <bananeweizen@gmx.de> | 2012-03-10 21:46:32 +0100 |
|---|---|---|
| committer | Bananeweizen <bananeweizen@gmx.de> | 2012-03-10 21:46:32 +0100 |
| commit | 491fdefaf2ab8adff5c62898fa00f9af2220b214 (patch) | |
| tree | 9091734e7f8786b8fcc5f1adc3cf23e822c9eae8 | |
| parent | d40313cf48a9956487afd2b591b34d588bb4ae82 (diff) | |
| download | cgeo-491fdefaf2ab8adff5c62898fa00f9af2220b214.zip cgeo-491fdefaf2ab8adff5c62898fa00f9af2220b214.tar.gz cgeo-491fdefaf2ab8adff5c62898fa00f9af2220b214.tar.bz2 | |
fix #1262: icon decoding does not work well
6 files changed, 259 insertions, 212 deletions
diff --git a/main/src/cgeo/geocaching/connector/gc/GCBase.java b/main/src/cgeo/geocaching/connector/gc/GCBase.java index 7780ba2..127b773 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCBase.java +++ b/main/src/cgeo/geocaching/connector/gc/GCBase.java @@ -30,8 +30,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * GC.com/Groundspeak (GS) specific stuff @@ -47,25 +45,6 @@ public class GCBase { protected final static long GC_BASE31 = 31; protected final static long GC_BASE16 = 16; - // Pixel colors in tile - private final static int BORDER_GRAY = 0x5F5F5F; - private final static int DARK_GREEN = 0x316013; // Tradi 14 - private final static int LIGHT_GREEN = 0x80AF64; // Tradi 13 - private final static int DARK_BLUE = 0x243C97; // Mystery - private final static int YELLOW = 0xFFDE19; // Multi 14,13 - private final static int FOUND = 0xFBEA5D; // Found - - // Offset inside cache icon - private final static int POSX_TRADI = 7; - private final static int POSY_TRADI = -12; - private final static int POSX_MULTI = 5; // for orange 8 - private final static int POSY_MULTI = -9; // for orange 10 - private final static int POSX_MYSTERY = 5; - private final static int POSY_MYSTERY = -13; - private final static int POSX_FOUND = 10; - private final static int POSY_FOUND = -8; - - private final static Pattern PATTERN_JSON_KEY = Pattern.compile("[^\\d]*" + "(\\d+),\\s*(\\d+)" + "[^\\d]*"); // (12, 34) /** * Searches the view port on the live map with Strategy.AUTO * @@ -235,7 +214,7 @@ public class GCBase { for (int i = 1; i < keys.length(); i++) { // index 0 is empty String key = keys.getString(i); if (StringUtils.isNotBlank(key)) { - int[] xy = splitJSONKey(key); + UTFGridPosition pos = UTFGridPosition.fromString(key); JSONArray dataForKey = dataObject.getJSONArray(key); for (int j = 0; j < dataForKey.length(); j++) { JSONObject cacheInfo = dataForKey.getJSONObject(j); @@ -245,14 +224,10 @@ public class GCBase { List<UTFGridPosition> listOfPositions = positions.get(id); if (listOfPositions == null) { listOfPositions = new ArrayList<UTFGridPosition>(); + positions.put(id, listOfPositions); } - /* - * Optimization - * UTFGridPosition pos = keyPositions.get(key); - */ - UTFGridPosition pos = new UTFGridPosition(xy[0], xy[1]); + listOfPositions.add(pos); - positions.put(id, listOfPositions); } } } @@ -269,9 +244,9 @@ public class GCBase { cache.setCoords(tile.getCoord(xy)); if (strategy.flags.contains(StrategyFlag.PARSE_TILES)) { if (tile.getZoomlevel() >= 14) { - parseMapPNG14(cache, bitmap, xy); + IconDecoder.parseMapPNG14(cache, bitmap, xy); } else { - parseMapPNG13(cache, bitmap, xy); + IconDecoder.parseMapPNG13(cache, bitmap, xy); } } else { cache.setType(CacheType.UNKNOWN); @@ -287,89 +262,6 @@ public class GCBase { return searchResult; } - // Try to get the cache type from the PNG image for a tile */ - public static void parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - int x = xy.getX() * 4 + 2; - int y = xy.getY() * 4 + 2; - int countX = 0; - int countY = 0; - - // search for left border - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != BORDER_GRAY) { - if (--x < 0 || ++countX > 20) { - return; - } - } - // search for bottom border - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != 0x000000) { - if (++y >= Tile.TILE_SIZE || ++countY > 20) { - return; - } - } - - try { - if ((bitmap.getPixel(x + POSX_TRADI, y + POSY_TRADI) & 0x00FFFFFF) == DARK_GREEN) { - cache.setType(CacheType.TRADITIONAL); - return; - } - if ((bitmap.getPixel(x + POSX_MYSTERY, y + POSY_MYSTERY) & 0x00FFFFFF) == DARK_BLUE) { - cache.setType(CacheType.MYSTERY); - return; - } - if ((bitmap.getPixel(x + POSX_MULTI, y + POSY_MULTI) & 0x00FFFFFF) == YELLOW) { - cache.setType(CacheType.MULTI); - return; - } - if ((bitmap.getPixel(x + POSX_FOUND, y + POSY_FOUND) & 0x00FFFFFF) == FOUND) { - cache.setFound(true); - return; - } - } catch (IllegalArgumentException e) { - // intentionally left blank - } - - return; - } - - // Try to get the cache type from the PNG image for a tile */ - public static void parseMapPNG13(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - - int x = xy.getX() * 4 + 2; - int y = xy.getY() * 4 + 2; - int countY = 0; - - // search for top border - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != BORDER_GRAY) { - if (--y < 0 || ++countY > 12) { - return; - } - } - - try { - int color = bitmap.getPixel(x, y + 2) & 0x00FFFFFF; - - switch (color) { - case LIGHT_GREEN: - cache.setType(CacheType.TRADITIONAL); - return; - case YELLOW: - cache.setType(CacheType.MULTI); - return; - } - if ((color | 0x00FFFF) == 0x00FFFF) { // BLUE - cache.setType(CacheType.MYSTERY); - return; - } - // Found consists out of too many different colors - } - catch (IllegalArgumentException e) { - // intentionally left blank - } - - return; - } - - /** * Calculate needed tiles for the given viewport @@ -478,22 +370,4 @@ public class GCBase { return new String[] { userSession, sessionToken }; } - /** - * @param key - * Key in the format (xx, xx) - * @return - */ - static int[] splitJSONKey(String key) { - final Matcher matcher = PATTERN_JSON_KEY.matcher(key); - try { - if (matcher.matches()) { - final int x = Integer.parseInt(matcher.group(1)); - final int y = Integer.parseInt(matcher.group(2)); - return new int[] { x, y }; - } - } catch (NumberFormatException e) { - } - return new int[] { 0, 0 }; - } - } diff --git a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java new file mode 100644 index 0000000..2935ef0 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java @@ -0,0 +1,156 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.enumerations.CacheType; + +import android.graphics.Bitmap; + +/** + * icon decoder for cache icons + * + */ +public abstract class IconDecoder { + + private static final int[] OFFSET_X = new int[] { 0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, -2, -2, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2 }; + private static final int[] OFFSET_Y = new int[] { 0, 0, 1, 1, 1, 0, -1, -1, -1, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2, -2, -2, -2 }; + + /** + * The icon decoder walks a spiral around the center pixel position of the cache + * and searches for characteristic colors. + * + * @param cache + * @param bitmap + * @param xy + */ + public static void parseMapPNG13(final cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + final int xCenter = xy.getX() * 4 + 2; + final int yCenter = xy.getY() * 4 + 2; + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + + int countMulti = 0; + int countFound = 0; + + for (int i = 0; i < OFFSET_X.length; i++) { + + // assert that we are still in the tile + final int x = xCenter + OFFSET_X[i]; + if (x < 0 || x >= bitmapWidth) { + continue; + } + + final int y = yCenter + OFFSET_Y[i]; + if (y < 0 || y >= bitmapHeight) { + continue; + } + + int color = bitmap.getPixel(x, y) & 0x00FFFFFF; + + // transparent pixels are not interesting + if (color == 0) { + continue; + } + + int red = (color & 0xFF0000) >> 16; + int green = (color & 0xFF00) >> 8; + int blue = color & 0xFF; + + // these are quite sure, so one pixel is enough for matching + if (green > 0x80 && green > red && green > blue) { + cache.setType(CacheType.TRADITIONAL); + return; + } + if (blue > 0x80 && blue > red && blue > green) { + cache.setType(CacheType.MYSTERY); + return; + } + if (red > 0x90 && blue < 0x10 && green < 0x10) { + cache.setType(CacheType.EVENT); + return; + } + + // next two are hard to distinguish, therefore we sample all pixels of the spiral + if (red > 0xFA && green > 0xD0) { + countMulti++; + } + if (red < 0xF3 && red > 0xa0 && green > 0x20 && blue < 0x80) { + countFound++; + } + } + + // now check whether we are sure about found/multi + if (countFound > countMulti && countFound >= 2) { + cache.setFound(true); + } + if (countMulti > countFound && countMulti >= 5) { + cache.setType(CacheType.MULTI); + } + } + + // Pixel colors in tile + private final static int COLOR_BORDER_GRAY = 0x5F5F5F; + private final static int COLOR_TRADITIONAL = 0x316013; + private final static int COLOR_MYSTERY = 0x243C97; + private final static int COLOR_MULTI = 0xFFDE19; + private final static int COLOR_FOUND = 0xFBEA5D; + + // Offset inside cache icon + private final static int POSX_TRADI = 7; + private final static int POSY_TRADI = -12; + private final static int POSX_MULTI = 5; // for orange 8 + private final static int POSY_MULTI = -9; // for orange 10 + private final static int POSX_MYSTERY = 5; + private final static int POSY_MYSTERY = -13; + private final static int POSX_FOUND = 10; + private final static int POSY_FOUND = -8; + + /** + * For level 14 find the borders of the icons and then use a single pixel and color to match. + * + * @param cache + * @param bitmap + * @param xy + */ + public static void parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + int x = xy.getX() * 4 + 2; + int y = xy.getY() * 4 + 2; + + // search for left border + int countX = 0; + while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != COLOR_BORDER_GRAY) { + if (--x < 0 || ++countX > 20) { + return; + } + } + // search for bottom border + int countY = 0; + while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != 0x000000) { + if (++y >= Tile.TILE_SIZE || ++countY > 20) { + return; + } + } + + try { + if ((bitmap.getPixel(x + POSX_TRADI, y + POSY_TRADI) & 0x00FFFFFF) == COLOR_TRADITIONAL) { + cache.setType(CacheType.TRADITIONAL); + return; + } + if ((bitmap.getPixel(x + POSX_MYSTERY, y + POSY_MYSTERY) & 0x00FFFFFF) == COLOR_MYSTERY) { + cache.setType(CacheType.MYSTERY); + return; + } + if ((bitmap.getPixel(x + POSX_MULTI, y + POSY_MULTI) & 0x00FFFFFF) == COLOR_MULTI) { + cache.setType(CacheType.MULTI); + return; + } + if ((bitmap.getPixel(x + POSX_FOUND, y + POSY_FOUND) & 0x00FFFFFF) == COLOR_FOUND) { + cache.setFound(true); + return; + } + } catch (IllegalArgumentException e) { + // intentionally left blank + } + + return; + } +} diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java index e88a425..1aae560 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java @@ -1,5 +1,8 @@ package cgeo.geocaching.connector.gc; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Representation of a position inside an UTFGrid @@ -11,6 +14,7 @@ public final class UTFGridPosition { public final int x; public final int y; + private final static Pattern PATTERN_JSON_KEY = Pattern.compile("[^\\d]*" + "(\\d+),\\s*(\\d+)" + "[^\\d]*"); // (12, 34) public UTFGridPosition(final int x, final int y) { assert x >= 0 && x <= UTFGrid.GRID_MAXX : "x outside bounds"; @@ -28,4 +32,22 @@ public final class UTFGridPosition { return y; } + /** + * @param key + * Key in the format (xx, xx) + * @return + */ + static UTFGridPosition fromString(String key) { + final Matcher matcher = UTFGridPosition.PATTERN_JSON_KEY.matcher(key); + try { + if (matcher.matches()) { + final int x = Integer.parseInt(matcher.group(1)); + final int y = Integer.parseInt(matcher.group(2)); + return new UTFGridPosition(x, y); + } + } catch (NumberFormatException e) { + } + return new UTFGridPosition(0, 0); + } + } diff --git a/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java b/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java index 5f07f60..32ab1f2 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java @@ -1,14 +1,18 @@ package cgeo.geocaching.connector.gc; -import java.util.Arrays; - import junit.framework.TestCase; public class GCBaseTest extends TestCase { public static void testSplitJSONKey() { - assertTrue(Arrays.equals(new int[] { 1, 2 }, GCBase.splitJSONKey("(1, 2)"))); - assertTrue(Arrays.equals(new int[] { 12, 34 }, GCBase.splitJSONKey("(12, 34)"))); - assertTrue(Arrays.equals(new int[] { 1234, 56 }, GCBase.splitJSONKey("(1234,56)"))); - assertTrue(Arrays.equals(new int[] { 1234, 567 }, GCBase.splitJSONKey("(1234, 567)"))); + assertKey("(1, 2)", 1, 2); + assertKey("(12, 34)", 12, 34); + assertKey("(1234,56)", 1234, 56); + assertKey("(1234, 567)", 1234, 567); + } + + private static void assertKey(String key, int x, int y) { + UTFGridPosition pos = UTFGridPosition.fromString(key); + assertEquals(x, pos.getX()); + assertEquals(y, pos.getY()); } } diff --git a/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java b/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java index 3696568..dfa9a8b 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java @@ -1,19 +1,11 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.Login; import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; -import cgeo.geocaching.test.R; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.Log; public class GCConnectorTest extends AbstractResourceInstrumentationTestCase { @@ -50,78 +42,25 @@ public class GCConnectorTest extends AbstractResourceInstrumentationTestCase { /** Tile computation with different zoom levels */ public static void testTile() { - { - // http://coord.info/GC2CT8K = N 52° 30.462 E 013° 27.906 - Tile tile = new Tile(new Geopoint(52.5077, 13.4651), 14); - assertEquals(8804, tile.getX()); - assertEquals(5374, tile.getY()); - } - { - // (8633, 5381); N 52° 24,516 E 009° 42,592 - Tile tile = new Tile(new Geopoint("N 52° 24,516 E 009° 42,592"), 14); - assertEquals(8633, tile.getX()); - assertEquals(5381, tile.getY()); - } - { - // Hannover, GC22VTB UKM Memorial Tour - Tile tile = new Tile(new Geopoint("N 52° 22.177 E 009° 45.385"), 12); - assertEquals(2159, tile.getX()); - assertEquals(1346, tile.getY()); - } - { - // Seatle, GCK25B Groundspeak Headquarters - Tile tile = new Tile(new Geopoint("N 47° 38.000 W 122° 20.000"), 15); - assertEquals(5248, tile.getX()); - assertEquals(11440, tile.getY()); - } - { - // Sydney, GCXT2R Victoria Cross - Tile tile = new Tile(new Geopoint("S 33° 50.326 E 151° 12.426"), 13); - assertEquals(7536, tile.getX()); - assertEquals(4915, tile.getY()); - } - } + // http://coord.info/GC2CT8K = N 52° 30.462 E 013° 27.906 + assertTileAt(8804, 5374, new Tile(new Geopoint(52.5077, 13.4651), 14)); - public void testparseMapPNG() { - // createApplication(); - // cgBase.initialize(getApplication()); + // (8633, 5381); N 52° 24,516 E 009° 42,592 + assertTileAt(8633, 5381, new Tile(new Geopoint("N 52° 24,516 E 009° 42,592"), 14)); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - Bitmap bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getResources().openRawResource(R.raw.tile14)); - assert bitmap.getWidth() == Tile.TILE_SIZE : "Wrong size"; + // Hannover, GC22VTB UKM Memorial Tour + assertTileAt(2159, 1346, new Tile(new Geopoint("N 52° 22.177 E 009° 45.385"), 12)); - Log.d(Settings.tag, "Bitmap=" + bitmap.getWidth() + "x" + bitmap.getHeight()); + // Seattle, GCK25B Groundspeak Headquarters + assertTileAt(5248, 11440, new Tile(new Geopoint("N 47° 38.000 W 122° 20.000"), 15)); - cgCache cache = new cgCache(); - - // Tradi - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(97 / 4, 136 / 4)); - assertEquals(CacheType.TRADITIONAL, cache.getType()); - // Mystery - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(226 / 4, 104 / 4)); - assertEquals(CacheType.MYSTERY, cache.getType()); - // Multi - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(54 / 4, 97 / 4)); - assertEquals(CacheType.MULTI, cache.getType()); - // Found - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(119 / 4, 108 / 4)); - assertTrue(cache.isFound()); - cache.setFound(false); // reset - - bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getResources().openRawResource(R.raw.tile13)); - - // Tradi - GCBase.parseMapPNG13(cache, bitmap, new UTFGridPosition(146 / 4, 225 / 4)); - assertEquals(CacheType.TRADITIONAL, cache.getType()); - // Mystery - GCBase.parseMapPNG13(cache, bitmap, new UTFGridPosition(181 / 4, 116 / 4)); - assertEquals(CacheType.MYSTERY, cache.getType()); - // Multi - GCBase.parseMapPNG13(cache, bitmap, new UTFGridPosition(118 / 4, 230 / 4)); - assertEquals(CacheType.MULTI, cache.getType()); - // Found - not available in parseMapPNG13 + // Sydney, GCXT2R Victoria Cross + assertTileAt(7536, 4915, new Tile(new Geopoint("S 33° 50.326 E 151° 12.426"), 13)); } + private static void assertTileAt(int x, int y, final Tile tile) { + assertEquals(x, tile.getX()); + assertEquals(y, tile.getY()); + } } diff --git a/tests/src/cgeo/geocaching/connector/gc/IconDecoderTest.java b/tests/src/cgeo/geocaching/connector/gc/IconDecoderTest.java new file mode 100644 index 0000000..db6dc44 --- /dev/null +++ b/tests/src/cgeo/geocaching/connector/gc/IconDecoderTest.java @@ -0,0 +1,52 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.enumerations.CacheType; +import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; +import cgeo.geocaching.test.R; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +public class IconDecoderTest extends AbstractResourceInstrumentationTestCase { + + public void testparseMapPNG14() { + final Bitmap bitmap = getBitmap(R.raw.tile14); + Log.d(Settings.tag, "Bitmap=" + bitmap.getWidth() + "x" + bitmap.getHeight()); + + assertEquals(CacheType.TRADITIONAL, parse14(bitmap, 97 / 4, 136 / 4).getType()); + assertEquals(CacheType.MYSTERY, parse14(bitmap, 226 / 4, 104 / 4).getType()); + assertEquals(CacheType.MULTI, parse14(bitmap, 54 / 4, 97 / 4).getType()); + assertTrue(parse14(bitmap, 119 / 4, 108 / 4).isFound()); + } + + private Bitmap getBitmap(int resourceId) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + final Bitmap bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getResources().openRawResource(resourceId)); + assert bitmap.getWidth() == Tile.TILE_SIZE : "Wrong size"; + return bitmap; + } + + private static cgCache parse14(Bitmap bitmap, int x, int y) { + final cgCache cache = new cgCache(); + IconDecoder.parseMapPNG14(cache, bitmap, new UTFGridPosition(x, y)); + return cache; + } + + public void testParseMap13() { + final Bitmap bitmap = getBitmap(R.raw.tile13); + + assertEquals(CacheType.TRADITIONAL, parse13(bitmap, 146 / 4, 225 / 4).getType()); + assertEquals(CacheType.MYSTERY, parse13(bitmap, 181 / 4, 116 / 4).getType()); + assertEquals(CacheType.MULTI, parse13(bitmap, 118 / 4, 230 / 4).getType()); + } + + private static cgCache parse13(Bitmap bitmap, int x, int y) { + final cgCache cache = new cgCache(); + IconDecoder.parseMapPNG13(cache, bitmap, new UTFGridPosition(x, y)); + return cache; + } +} |
