| 1 |
|
|---|
| 2 |
void doMapTest() { |
|---|
| 3 |
println(); |
|---|
| 4 |
println("map test"); |
|---|
| 5 |
println(); |
|---|
| 6 |
|
|---|
| 7 |
Map m = new Map(new RoadProvider(), new Point(600, 600), new Coordinate(3165, 1313, 13), new Point(-144, -94)); |
|---|
| 8 |
Point p = m.locationPoint(new Location(37.804274, -122.262940)); |
|---|
| 9 |
println( p.toString().equals("(370.688, 342.438)") ); |
|---|
| 10 |
// !!! this is what the python version gives (probably floats vs doubles?) |
|---|
| 11 |
//println( p.toString().equals("(370.724, 342.549)") ); |
|---|
| 12 |
Location l = m.pointLocation(p); |
|---|
| 13 |
println( l.toString().equals("(37.804, -122.263)") ); |
|---|
| 14 |
} |
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
class MapCenter { |
|---|
| 19 |
|
|---|
| 20 |
Coordinate coordinate; |
|---|
| 21 |
Point point; |
|---|
| 22 |
|
|---|
| 23 |
MapCenter(Coordinate coordinate, Point point) { |
|---|
| 24 |
this.coordinate = coordinate; |
|---|
| 25 |
this.point = point; |
|---|
| 26 |
} |
|---|
| 27 |
|
|---|
| 28 |
} |
|---|
| 29 |
|
|---|
| 30 |
MapCenter calculateMapCenter(AbstractMapProvider provider, Coordinate centerCoord) { |
|---|
| 31 |
// Based on a center coordinate, returns the coordinate |
|---|
| 32 |
// of an initial tile and its point placement, relative to |
|---|
| 33 |
// the map center. |
|---|
| 34 |
|
|---|
| 35 |
// initial tile coordinate |
|---|
| 36 |
Coordinate initTileCoord = new Coordinate(floor(centerCoord.row), floor(centerCoord.column), floor(centerCoord.zoom)); |
|---|
| 37 |
|
|---|
| 38 |
// initial tile position, assuming centered tile well in grid |
|---|
| 39 |
float initX = (initTileCoord.column - centerCoord.column) * provider.tileWidth(); |
|---|
| 40 |
float initY = (initTileCoord.row - centerCoord.row) * provider.tileHeight(); |
|---|
| 41 |
Point initPoint = new Point(round(initX), round(initY)); |
|---|
| 42 |
|
|---|
| 43 |
return new MapCenter(initTileCoord, initPoint); |
|---|
| 44 |
} |
|---|
| 45 |
|
|---|
| 46 |
MapCenter calculateMapExtent(AbstractMapProvider provider, int width, int height, Location[] locations) { |
|---|
| 47 |
|
|---|
| 48 |
float minRow = MAX_FLOAT; |
|---|
| 49 |
float maxRow = -MAX_FLOAT; |
|---|
| 50 |
float minCol = MAX_FLOAT; |
|---|
| 51 |
float maxCol = -MAX_FLOAT; |
|---|
| 52 |
float minZoom = MAX_FLOAT; |
|---|
| 53 |
float maxZoom = -MAX_FLOAT; |
|---|
| 54 |
|
|---|
| 55 |
Coordinate[] coordinates = new Coordinate[locations.length]; |
|---|
| 56 |
for (int i = 0; i < coordinates.length; i++) { |
|---|
| 57 |
coordinates[i] = provider.locationCoordinate(locations[i]); |
|---|
| 58 |
minRow = min(minRow, coordinates[i].row); |
|---|
| 59 |
maxRow = max(maxRow, coordinates[i].row); |
|---|
| 60 |
minCol = min(minCol, coordinates[i].column); |
|---|
| 61 |
maxCol = max(maxCol, coordinates[i].column); |
|---|
| 62 |
minZoom = min(minZoom, coordinates[i].zoom); |
|---|
| 63 |
maxZoom = max(maxZoom, coordinates[i].zoom); |
|---|
| 64 |
} |
|---|
| 65 |
|
|---|
| 66 |
Coordinate TL = new Coordinate(minRow, minCol, minZoom); |
|---|
| 67 |
|
|---|
| 68 |
Coordinate BR = new Coordinate(maxRow, maxCol, maxZoom); |
|---|
| 69 |
|
|---|
| 70 |
// multiplication factor between horizontal span and map width |
|---|
| 71 |
float hFactor = (BR.column - TL.column) / ((float)width / provider.tileWidth()); |
|---|
| 72 |
|
|---|
| 73 |
// multiplication factor expressed as base-2 logarithm, for zoom difference |
|---|
| 74 |
float hZoomDiff = log(hFactor) / log(2); |
|---|
| 75 |
|
|---|
| 76 |
// possible horizontal zoom to fit geographical extent in map width |
|---|
| 77 |
float hPossibleZoom = TL.zoom - ceil(hZoomDiff); |
|---|
| 78 |
|
|---|
| 79 |
// multiplication factor between vertical span and map height |
|---|
| 80 |
float vFactor = (BR.row - TL.row) / ((float)height / provider.tileHeight()); |
|---|
| 81 |
|
|---|
| 82 |
// multiplication factor expressed as base-2 logarithm, for zoom difference |
|---|
| 83 |
float vZoomDiff = log(vFactor) / log(2); |
|---|
| 84 |
|
|---|
| 85 |
// possible vertical zoom to fit geographical extent in map height |
|---|
| 86 |
float vPossibleZoom = TL.zoom - ceil(vZoomDiff); |
|---|
| 87 |
|
|---|
| 88 |
// initial zoom to fit extent vertically and horizontally |
|---|
| 89 |
float initZoom = min(hPossibleZoom, vPossibleZoom); |
|---|
| 90 |
|
|---|
| 91 |
// additionally, make sure it's not outside the boundaries set by provider limits |
|---|
| 92 |
//initZoom = min(initZoom, provider.outerLimits()[1].zoom) |
|---|
| 93 |
//initZoom = max(initZoom, provider.outerLimits()[0].zoom) |
|---|
| 94 |
|
|---|
| 95 |
// coordinate of extent center |
|---|
| 96 |
float centerRow = (TL.row + BR.row) / 2.0; |
|---|
| 97 |
float centerColumn = (TL.column + BR.column) / 2.0; |
|---|
| 98 |
float centerZoom = (TL.zoom + BR.zoom) / 2.0; |
|---|
| 99 |
Coordinate centerCoord = new Coordinate(centerRow, centerColumn, centerZoom).zoomTo(initZoom); |
|---|
| 100 |
|
|---|
| 101 |
return calculateMapCenter(provider, centerCoord); |
|---|
| 102 |
} |
|---|
| 103 |
|
|---|
| 104 |
|
|---|
| 105 |
class TileRequest { |
|---|
| 106 |
|
|---|
| 107 |
// how many times to retry a failing tile |
|---|
| 108 |
int MAX_ATTEMPTS = 5; |
|---|
| 109 |
|
|---|
| 110 |
boolean done; |
|---|
| 111 |
AbstractMapProvider provider; |
|---|
| 112 |
Coordinate coord; |
|---|
| 113 |
Point offset; |
|---|
| 114 |
|
|---|
| 115 |
PImage imgs[]; |
|---|
| 116 |
|
|---|
| 117 |
TileRequest(AbstractMapProvider provider, Coordinate coord, Point offset) { |
|---|
| 118 |
this.done = false; |
|---|
| 119 |
this.provider = provider; |
|---|
| 120 |
this.coord = coord; |
|---|
| 121 |
this.offset = offset; |
|---|
| 122 |
} |
|---|
| 123 |
|
|---|
| 124 |
boolean loaded() { |
|---|
| 125 |
return this.done; |
|---|
| 126 |
} |
|---|
| 127 |
|
|---|
| 128 |
PImage[] images() { |
|---|
| 129 |
return imgs; |
|---|
| 130 |
} |
|---|
| 131 |
|
|---|
| 132 |
void load(boolean verbose) { |
|---|
| 133 |
load(verbose, 1); |
|---|
| 134 |
} |
|---|
| 135 |
|
|---|
| 136 |
void load(boolean verbose, int attempt) { |
|---|
| 137 |
if (done) { |
|---|
| 138 |
return; |
|---|
| 139 |
} |
|---|
| 140 |
|
|---|
| 141 |
String[] urls = provider.getTileUrls(coord); |
|---|
| 142 |
|
|---|
| 143 |
if (verbose) { |
|---|
| 144 |
print("Requesting "); |
|---|
| 145 |
print(join(urls, ", ")); |
|---|
| 146 |
println(" - attempt no. " + attempt);// + " in thread', thread.get_ident() |
|---|
| 147 |
} |
|---|
| 148 |
|
|---|
| 149 |
this.imgs = new PImage[urls.length]; |
|---|
| 150 |
|
|---|
| 151 |
// this is the time-consuming part |
|---|
| 152 |
try { |
|---|
| 153 |
for (int i = 0; i < urls.length; i++) { |
|---|
| 154 |
String type = urls[i].startsWith("http://mt") ? "png" : urls[i].startsWith("http://kh") ? "jpg" : null; |
|---|
| 155 |
if (type != null) { |
|---|
| 156 |
imgs[i] = loadImage(urls[i], type); |
|---|
| 157 |
} |
|---|
| 158 |
else { |
|---|
| 159 |
imgs[i] = loadImage(urls[i]); |
|---|
| 160 |
} |
|---|
| 161 |
} |
|---|
| 162 |
} |
|---|
| 163 |
catch (Exception e) { |
|---|
| 164 |
/* |
|---|
| 165 |
if verbose: |
|---|
| 166 |
print 'Failed', urls, '- attempt no.', attempt, 'in thread', thread.get_ident() |
|---|
| 167 |
|
|---|
| 168 |
if attempt < TileRequest.MAX_ATTEMPTS: |
|---|
| 169 |
time.sleep(1 * attempt) |
|---|
| 170 |
return self.load(lock, verbose, attempt+1) |
|---|
| 171 |
else: |
|---|
| 172 |
imgs = [None for url in urls] |
|---|
| 173 |
*/ |
|---|
| 174 |
} |
|---|
| 175 |
|
|---|
| 176 |
if (verbose) { |
|---|
| 177 |
print("Received "); |
|---|
| 178 |
print(join(urls,", ")); |
|---|
| 179 |
println(" - attempt no. " + attempt);// 'in thread', thread.get_ident() |
|---|
| 180 |
} |
|---|
| 181 |
|
|---|
| 182 |
/* if lock.acquire(): |
|---|
| 183 |
self.imgs = imgs |
|---|
| 184 |
self.done = True |
|---|
| 185 |
lock.release() */ |
|---|
| 186 |
} |
|---|
| 187 |
|
|---|
| 188 |
} |
|---|
| 189 |
|
|---|
| 190 |
|
|---|
| 191 |
class TileQueue extends Vector { |
|---|
| 192 |
// List of TileRequest objects, that's sensitive to when they're loaded. |
|---|
| 193 |
boolean pending() { |
|---|
| 194 |
for (int i = 0; i < size(); i++) { |
|---|
| 195 |
if (!((TileRequest)get(i)).loaded()) { |
|---|
| 196 |
return true; |
|---|
| 197 |
} |
|---|
| 198 |
} |
|---|
| 199 |
return false; |
|---|
| 200 |
} |
|---|
| 201 |
} |
|---|
| 202 |
|
|---|
| 203 |
class Map { |
|---|
| 204 |
|
|---|
| 205 |
AbstractMapProvider provider; |
|---|
| 206 |
Point dimensions; |
|---|
| 207 |
Coordinate coordinate; |
|---|
| 208 |
Point offset; |
|---|
| 209 |
|
|---|
| 210 |
Map(AbstractMapProvider provider, Point dimensions, Location location, int zoom) { |
|---|
| 211 |
MapCenter center = calculateMapCenter(provider, provider.locationCoordinate(location).zoomTo(zoom)); |
|---|
| 212 |
this.provider = provider; |
|---|
| 213 |
this.dimensions = dimensions; |
|---|
| 214 |
this.coordinate = center.coordinate; |
|---|
| 215 |
this.offset = center.point; |
|---|
| 216 |
} |
|---|
| 217 |
|
|---|
| 218 |
Map(AbstractMapProvider provider, Point dimensions, Coordinate coordinate, Point offset) { |
|---|
| 219 |
// Instance of a map intended for drawing to an image. |
|---|
| 220 |
// provider |
|---|
| 221 |
// Instance of IMapProvider |
|---|
| 222 |
// dimensions |
|---|
| 223 |
// Size of output image, instance of Point |
|---|
| 224 |
// coordinate |
|---|
| 225 |
// Base tile, instance of Coordinate |
|---|
| 226 |
// offset |
|---|
| 227 |
// Position of base tile relative to map center, instance of Point |
|---|
| 228 |
|
|---|
| 229 |
this.provider = provider; |
|---|
| 230 |
this.dimensions = dimensions; |
|---|
| 231 |
this.coordinate = coordinate; |
|---|
| 232 |
this.offset = offset; |
|---|
| 233 |
} |
|---|
| 234 |
|
|---|
| 235 |
String toString() { |
|---|
| 236 |
return "Map(" + provider + ", " + dimensions + ", " + coordinate + ", " + offset + ")"; |
|---|
| 237 |
} |
|---|
| 238 |
|
|---|
| 239 |
Point locationPoint(Location location) { |
|---|
| 240 |
// Return an x, y point on the map image for a given geographical location. |
|---|
| 241 |
|
|---|
| 242 |
Point point = new Point(offset.x, offset.y); |
|---|
| 243 |
Coordinate coord = provider.locationCoordinate(location).zoomTo(coordinate.zoom); |
|---|
| 244 |
|
|---|
| 245 |
// distance from the known coordinate offset |
|---|
| 246 |
point.x += provider.tileWidth() * (coord.column - coordinate.column); |
|---|
| 247 |
point.y += provider.tileHeight() * (coord.row - coordinate.row); |
|---|
| 248 |
|
|---|
| 249 |
// because of the center/corner business |
|---|
| 250 |
point.x += dimensions.x/2.0; |
|---|
| 251 |
point.y += dimensions.y/2.0; |
|---|
| 252 |
|
|---|
| 253 |
return point; |
|---|
| 254 |
} |
|---|
| 255 |
|
|---|
| 256 |
Location pointLocation(Point point) { |
|---|
| 257 |
// Return a geographical location on the map image for a given x, y point. |
|---|
| 258 |
|
|---|
| 259 |
Coordinate hizoomCoord = coordinate.zoomTo(coordinate.MAX_ZOOM); |
|---|
| 260 |
|
|---|
| 261 |
// because of the center/corner business |
|---|
| 262 |
point = new Point(point.x - dimensions.x/2.0, |
|---|
| 263 |
point.y - dimensions.y/2.0); |
|---|
| 264 |
|
|---|
| 265 |
// distance in tile widths from reference tile to point |
|---|
| 266 |
float xTiles = (point.x - offset.x) / provider.tileWidth(); |
|---|
| 267 |
float yTiles = (point.y - offset.y) / provider.tileHeight(); |
|---|
| 268 |
|
|---|
| 269 |
// distance in rows & columns at maximum zoom |
|---|
| 270 |
float xDistance = xTiles * pow(2, (coordinate.MAX_ZOOM - coordinate.zoom)); |
|---|
| 271 |
float yDistance = yTiles * pow(2, (coordinate.MAX_ZOOM - coordinate.zoom)); |
|---|
| 272 |
|
|---|
| 273 |
// new point coordinate reflecting that distance |
|---|
| 274 |
Coordinate coord = new Coordinate(round(hizoomCoord.row + yDistance), round(hizoomCoord.column + xDistance), hizoomCoord.zoom); |
|---|
| 275 |
|
|---|
| 276 |
coord = coord.zoomTo(coordinate.zoom); |
|---|
| 277 |
|
|---|
| 278 |
Location location = provider.coordinateLocation(coord); |
|---|
| 279 |
|
|---|
| 280 |
return location; |
|---|
| 281 |
} |
|---|
| 282 |
|
|---|
| 283 |
PImage draw_bbox(float[] bbox) { |
|---|
| 284 |
return draw_bbox(bbox, 16, false); |
|---|
| 285 |
} |
|---|
| 286 |
|
|---|
| 287 |
PImage draw_bbox(float[] bbox, int zoom) { |
|---|
| 288 |
return draw_bbox(bbox, zoom, false); |
|---|
| 289 |
} |
|---|
| 290 |
|
|---|
| 291 |
// bbox = new float[] { south, west, north, east }; |
|---|
| 292 |
PImage draw_bbox(float[] bbox, int zoom, boolean verbose) { |
|---|
| 293 |
|
|---|
| 294 |
Location sw = new Location(bbox[0], bbox[1]); |
|---|
| 295 |
Location ne = new Location(bbox[2], bbox[3]); |
|---|
| 296 |
Location nw = new Location(ne.lat, sw.lon); |
|---|
| 297 |
Location se = new Location(sw.lat, ne.lon); |
|---|
| 298 |
|
|---|
| 299 |
Coordinate TL = provider.locationCoordinate(nw).zoomTo(zoom); |
|---|
| 300 |
|
|---|
| 301 |
TileQueue tiles = new TileQueue(); |
|---|
| 302 |
|
|---|
| 303 |
float cur_lon = sw.lon; |
|---|
| 304 |
float cur_lat = ne.lat; |
|---|
| 305 |
float max_lon = ne.lon; |
|---|
| 306 |
float max_lat = sw.lat; |
|---|
| 307 |
|
|---|
| 308 |
float x_off = 0; |
|---|
| 309 |
float y_off = 0; |
|---|
| 310 |
float tile_x = 0; |
|---|
| 311 |
float tile_y = 0; |
|---|
| 312 |
|
|---|
| 313 |
Coordinate tileCoord = TL.copy(); |
|---|
| 314 |
|
|---|
| 315 |
while (cur_lon < max_lon) { |
|---|
| 316 |
|
|---|
| 317 |
y_off = 0; |
|---|
| 318 |
tile_y = 0; |
|---|
| 319 |
|
|---|
| 320 |
Location loc; |
|---|
| 321 |
while (cur_lat > max_lat) { |
|---|
| 322 |
|
|---|
| 323 |
tiles.add(new TileRequest(provider, tileCoord, new Point(x_off, y_off))); |
|---|
| 324 |
y_off += provider.tileHeight(); |
|---|
| 325 |
|
|---|
| 326 |
tileCoord = tileCoord.down(); |
|---|
| 327 |
loc = provider.coordinateLocation(tileCoord); |
|---|
| 328 |
cur_lat = loc.lat; |
|---|
| 329 |
|
|---|
| 330 |
tile_y += 1; |
|---|
| 331 |
} |
|---|
| 332 |
|
|---|
| 333 |
x_off += provider.tileWidth(); |
|---|
| 334 |
cur_lat = ne.lat; |
|---|
| 335 |
|
|---|
| 336 |
tile_x += 1; |
|---|
| 337 |
tileCoord = TL.copy().right(tile_x); |
|---|
| 338 |
|
|---|
| 339 |
loc = provider.coordinateLocation(tileCoord); |
|---|
| 340 |
cur_lon = loc.lon; |
|---|
| 341 |
} |
|---|
| 342 |
|
|---|
| 343 |
int width = (int)floor(provider.tileWidth() * tile_x); |
|---|
| 344 |
int height = (int)floor(provider.tileHeight() * tile_y); |
|---|
| 345 |
|
|---|
| 346 |
// Quick, look over there! |
|---|
| 347 |
|
|---|
| 348 |
MapCenter center = calculateMapExtent(provider, width, height, new Location[] { |
|---|
| 349 |
new Location(bbox[0], bbox[1]), new Location(bbox[2], bbox[3]) } |
|---|
| 350 |
); |
|---|
| 351 |
|
|---|
| 352 |
this.offset = center.point; |
|---|
| 353 |
this.coordinate = center.coordinate; |
|---|
| 354 |
this.dimensions = new Point(width, height); |
|---|
| 355 |
|
|---|
| 356 |
return draw(); |
|---|
| 357 |
} |
|---|
| 358 |
|
|---|
| 359 |
PGraphics draw() { |
|---|
| 360 |
return draw(false); |
|---|
| 361 |
} |
|---|
| 362 |
|
|---|
| 363 |
PGraphics draw(boolean verbose) { |
|---|
| 364 |
// Draw map out to a PImage and return it. |
|---|
| 365 |
|
|---|
| 366 |
Coordinate coord = coordinate.copy(); |
|---|
| 367 |
Point corner = new Point(int(offset.x + dimensions.x/2.0), int(offset.y + dimensions.y/2.0)); |
|---|
| 368 |
|
|---|
| 369 |
while (corner.x > 0) { |
|---|
| 370 |
corner.x -= provider.tileWidth(); |
|---|
| 371 |
coord = coord.left(); |
|---|
| 372 |
} |
|---|
| 373 |
|
|---|
| 374 |
while (corner.y > 0) { |
|---|
| 375 |
corner.y -= provider.tileHeight(); |
|---|
| 376 |
coord = coord.up(); |
|---|
| 377 |
} |
|---|
| 378 |
|
|---|
| 379 |
TileQueue tiles = new TileQueue(); |
|---|
| 380 |
|
|---|
| 381 |
Coordinate rowCoord = coord.copy(); |
|---|
| 382 |
for (float y = corner.y; y < dimensions.y; y += provider.tileHeight()) { |
|---|
| 383 |
Coordinate tileCoord = rowCoord.copy(); |
|---|
| 384 |
for (float x = corner.x; x < dimensions.x; x += provider.tileWidth()) { |
|---|
| 385 |
tiles.add(new TileRequest(provider, tileCoord, new Point(x, y))); |
|---|
| 386 |
tileCoord = tileCoord.right(); |
|---|
| 387 |
} |
|---|
| 388 |
rowCoord = rowCoord.down(); |
|---|
| 389 |
} |
|---|
| 390 |
|
|---|
| 391 |
return render_tiles(tiles, (int)dimensions.x, (int)dimensions.y, verbose); |
|---|
| 392 |
|
|---|
| 393 |
} |
|---|
| 394 |
|
|---|
| 395 |
PGraphics render_tiles(TileQueue tiles, int img_width, int img_height) { |
|---|
| 396 |
return render_tiles(tiles, img_width, img_height, false); |
|---|
| 397 |
} |
|---|
| 398 |
|
|---|
| 399 |
PGraphics render_tiles(TileQueue tiles, int img_width, int img_height, boolean verbose) { |
|---|
| 400 |
|
|---|
| 401 |
// lock = thread.allocate_lock() |
|---|
| 402 |
|
|---|
| 403 |
for (int i = 0; i < tiles.size(); i++) { |
|---|
| 404 |
TileRequest tile = (TileRequest)tiles.get(i); |
|---|
| 405 |
// request all needed images |
|---|
| 406 |
// thread.start_new_thread(tile.load, (lock, verbose)) |
|---|
| 407 |
tile.load(verbose); |
|---|
| 408 |
} |
|---|
| 409 |
|
|---|
| 410 |
// if it takes any longer than 20 sec overhead + 10 sec per tile, give up |
|---|
| 411 |
// due = time.time() + 20 + len(tiles) * 10 |
|---|
| 412 |
|
|---|
| 413 |
//while time.time() < due and tiles.pending(): |
|---|
| 414 |
// # hang around until they are loaded or we run out of time... |
|---|
| 415 |
// time.sleep(1) |
|---|
| 416 |
|
|---|
| 417 |
PGraphics mapImg = createGraphics(img_width, img_height, JAVA2D); |
|---|
| 418 |
mapImg.beginDraw(); |
|---|
| 419 |
|
|---|
| 420 |
for (int i = 0; i < tiles.size(); i++) { |
|---|
| 421 |
TileRequest tile = (TileRequest)tiles.get(i); |
|---|
| 422 |
for (int j = 0; j < tile.images().length; j++) { |
|---|
| 423 |
PImage img = tile.images()[j]; |
|---|
| 424 |
mapImg.image(img, tile.offset.x, tile.offset.y); |
|---|
| 425 |
} |
|---|
| 426 |
} |
|---|
| 427 |
|
|---|
| 428 |
mapImg.endDraw(); |
|---|
| 429 |
|
|---|
| 430 |
return mapImg; |
|---|
| 431 |
} |
|---|
| 432 |
|
|---|
| 433 |
} |
|---|