1 import gov.nasa.worldwind.avlist.AVKey;
2 import gov.nasa.worldwind.avlist.AVList;
3 import gov.nasa.worldwind.avlist.AVListImpl;
4 import gov.nasa.worldwind.geom.Angle;
5 import gov.nasa.worldwind.geom.LatLon;
6 import gov.nasa.worldwind.render.DrawContext;
7 import gov.nasa.worldwind.util.LevelSet;
8 import gov.nasa.worldwind.util.Tile;
9 import gov.nasa.worldwind.util.TileUrlBuilder;
10 import gov.nasa.worldwind.layers.Mercator.*;
11
12 import java.awt.image.BufferedImage;
13 import java.net.MalformedURLException;
14 import java.net.URL;
15
16 public class VirtualEarthLayer extends BasicMercatorTiledImageLayer
17 {
18 public static enum Dataset
19 {
20 AERIAL("Aerial", "a", ".jpg"),
21 HYBRID("Hybrid", "h", ".jpg"),
22 ROAD("Road", "r", ".png");
23
24 public final String label;
25 public final String dataset;
26 public final String formatSuffix;
27
28 private Dataset(String label, String dataset, String formatSuffix)
29 {
30 this.label = label;
31 this.dataset = dataset;
32 this.formatSuffix = formatSuffix;
33 }
34 }
35
36 // private VirtualEarthLogo logo = new VirtualEarthLogo();
37 private final Dataset dataset;
38
39 public VirtualEarthLayer()
40 {
41 this(Dataset.HYBRID);
42 }
43
44 public VirtualEarthLayer(Dataset dataset)
45 {
46 super(makeLevels(dataset));
47 if (dataset == null)
48 throw new NullPointerException("Dataset cannot be null");
49 this.dataset = dataset;
50 this.setValue(AVKey.DISPLAY_NAME, "Microsoft Virtual Earth "
51 + dataset.label);
52 // this.setSplitScale(1.3);
53 }
54
55 protected static LevelSet makeLevels(Dataset dataset)
56 {
57 AVList params = new AVListImpl();
58
59 params.setValue(AVKey.TILE_WIDTH, 256);
60 params.setValue(AVKey.TILE_HEIGHT, 256);
61 params.setValue(AVKey.DATA_CACHE_NAME, "Earth/MS Virtual Earth Mercator/MSVE "
62 + dataset.label);
63 params.setValue(AVKey.SERVICE,
64 "http://a0.ortho.tiles.virtualearth.net/tiles/");
65 params.setValue(AVKey.DATASET_NAME, dataset.dataset);
66 params.setValue(AVKey.FORMAT_SUFFIX, dataset.formatSuffix);
67 params.setValue(AVKey.NUM_LEVELS, 16);
68 params.setValue(AVKey.NUM_EMPTY_LEVELS, 0);
69 params.setValue(AVKey.LEVEL_ZERO_TILE_DELTA, new LatLon(Angle
70 .fromDegrees(22.5d), Angle.fromDegrees(45d)));
71 params.setValue(AVKey.SECTOR, new MercatorSector(-1.0, 1.0,
72 Angle.NEG180, Angle.POS180));
73 params.setValue(AVKey.TILE_URL_BUILDER, new URLBuilder());
74 params.setValue(AVKey.DISPLAY_NAME, "Microsoft Virtual Earth "
75 + dataset.label);
76
77 return new LevelSet(params);
78 }
79
80 private static class URLBuilder implements TileUrlBuilder
81 {
82 public URL getURL(Tile tile, String imageFormat)
83 throws MalformedURLException
84 {
85 String quadkey = tileToQuadKey(tile.getColumn(), tile.getRow(),
86 tile.getLevelNumber() + 2);
87 return new URL(tile.getLevel().getService()
88 + tile.getLevel().getDataset() + quadkey + ".jpeg?g=1");
89 }
90 }
91
92 protected static String tileToQuadKey(int col, int row, int level)
93 {
94 String quad = "";
95 for (int i = level; i >= 0; i--)
96 {
97 int mask = 1 << i;
98 int cell = 0;
99 if ((col & mask) != 0)
100 {
101 cell++;
102 }
103 if ((row & mask) == 0)
104 {
105 cell += 2;
106 }
107 quad += cell;
108 }
109 return quad;
110 }
111
112 @Override
113 public void render(DrawContext dc)
114 {
115 super.render(dc);
116 /*
117 if (isEnabled())
118 {
119 dc.addOrderedRenderable(logo);
120 }
121 */
122 }
123
124 @Override
125 protected boolean isTileValid(BufferedImage image)
126 {
127 //return false if the tile is white (this will mark the tile as absent)
128 boolean white = true;
129 //JPEG compression will cause white to be not quite white
130 String lowercaseFormat = getDataset().formatSuffix.toLowerCase();
131 int threshold = lowercaseFormat.contains("jpg")
132 || lowercaseFormat.contains("jpeg") ? 200 : 250;
133 for (int x = 0; x < image.getWidth(); x++)
134 {
135 for (int y = 0; y < image.getHeight(); y++)
136 {
137 int rgb = image.getRGB(x, y);
138 white = isWhite(rgb, threshold);
139 if (!white)
140 break;
141 }
142 if (!white)
143 break;
144 }
145 return !white;
146 }
147
148 private boolean isWhite(int rgb, int threshold)
149 {
150 int r = (rgb >> 16) & 0xff;
151 int g = (rgb >> 8) & 0xff;
152 int b = (rgb >> 0) & 0xff;
153 return r + b + g > threshold * 3;
154 }
155
156 public Dataset getDataset()
157 {
158 return dataset;
159 }
160 }