001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wms/dataaccess/OSMSlippyMapReader.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 037 package org.deegree.ogcwebservices.wms.dataaccess; 038 039 import java.awt.Graphics; 040 import java.awt.image.BufferedImage; 041 import java.awt.image.renderable.ParameterBlock; 042 import java.io.IOException; 043 import java.io.InputStream; 044 import java.net.URL; 045 import java.util.ArrayList; 046 import java.util.Iterator; 047 import java.util.LinkedHashMap; 048 import java.util.List; 049 import java.util.Map; 050 import java.util.Properties; 051 052 import javax.media.jai.Interpolation; 053 import javax.media.jai.InterpolationBilinear; 054 import javax.media.jai.JAI; 055 import javax.media.jai.RenderedOp; 056 057 import org.deegree.framework.log.ILogger; 058 import org.deegree.framework.log.LoggerFactory; 059 import org.deegree.framework.util.ImageUtils; 060 import org.deegree.framework.util.MapUtils; 061 import org.deegree.framework.xml.XMLFragment; 062 import org.deegree.model.crs.CRSFactory; 063 import org.deegree.model.crs.CoordinateSystem; 064 import org.deegree.model.crs.GeoTransformer; 065 import org.deegree.model.crs.UnknownCRSException; 066 import org.deegree.model.spatialschema.Envelope; 067 import org.deegree.model.spatialschema.GeometryFactory; 068 import org.deegree.model.spatialschema.Point; 069 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo; 070 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult; 071 import org.deegree.ogcwebservices.wms.operation.GetLegendGraphic; 072 import org.deegree.ogcwebservices.wms.operation.GetMap; 073 import org.deegree.ogcwebservices.wms.operation.GetMapResult; 074 075 /** 076 * TODO add class documentation here. 077 * 078 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 079 * @author last edited by: $Author: awietschke $ 080 * 081 * @version $Revision: 18845 $, $Date: 2009-07-31 16:43:08 +0200 (Fr, 31 Jul 2009) $ 082 */ 083 public class OSMSlippyMapReader implements ExternalRasterDataAccess { 084 085 private static final ILogger LOG = LoggerFactory.getLogger( OSMSlippyMapReader.class ); 086 087 private static int tileSize = 256; 088 089 private static CoordinateSystem crsWGS84; 090 091 private static CoordinateSystem crsOSM; 092 093 private static GeoTransformer gt2OSM; 094 095 private static GeoTransformer gt2WGS84; 096 097 private int width; 098 099 private int height; 100 101 private Envelope envelope; 102 103 private BufferedImage target; 104 105 private Envelope targetEnv; 106 107 private Properties props; 108 109 private BufferedImage legend; 110 111 private int minLevel = 0; 112 113 private int maxLevel = 18; 114 115 private String extension = ".png"; 116 117 // String base = "http://andy.sandbox.cloudmade.com/tiles/cycle/"; 118 // String base = "http://tile.openstreetmap.org/"; 119 // String base = "http://andy.sandbox.cloudmade.com/tiles/cycle/"; 120 121 static Map<double[], Integer> levels; 122 static { 123 if ( levels == null ) { 124 levels = new LinkedHashMap<double[], Integer>( 20 ); 125 double min = 0; 126 double max = 0.844517829; 127 for ( int i = 0; i < 19; i++ ) { 128 levels.put( new double[] { min, max }, 18 - i ); 129 min = max; 130 max *= 2d; 131 } 132 try { 133 crsWGS84 = CRSFactory.create( "EPSG:4326" ); 134 crsOSM = CRSFactory.create( "OSM_SLIPPY_MAP" ); 135 gt2OSM = new GeoTransformer( crsOSM ); 136 gt2WGS84 = new GeoTransformer( crsWGS84 ); 137 } catch ( UnknownCRSException e ) { 138 // TODO Auto-generated catch block 139 e.printStackTrace(); 140 } 141 } 142 } 143 144 /** 145 * default initialization of configuration 146 */ 147 private void init() { 148 if ( props == null ) { 149 props = new Properties(); 150 props.put( "TILEROOT", "http://tile.openstreetmap.org/" ); 151 props.put( "LEGEND", props.getProperty( "TILEROOT" ) + "14/8514/5504.png" ); 152 } 153 } 154 155 /** 156 * 157 * @param lat 158 * @param lon 159 * @param zoom 160 * @return 161 */ 162 private int[] getTileNumber( final double lon, final double lat, final int zoom ) { 163 int xtile = (int) Math.floor( ( lon + 180 ) / 360 * ( 1 << zoom ) ); 164 int ytile = (int) Math.floor( ( 1 - Math.log( Math.tan( lat * Math.PI / 180 ) + 1 165 / Math.cos( lat * Math.PI / 180 ) ) 166 / Math.PI ) 167 / 2 * ( 1 << zoom ) ); 168 169 return new int[] { xtile, ytile }; 170 } 171 172 private void readSlippyMaps() 173 throws Exception { 174 175 double scale = MapUtils.calcScale( width, height, envelope, crsWGS84, 1 ); 176 int level = calculateLevel( scale ); 177 178 List<List<String>> tiles = new ArrayList<List<String>>( 5 ); 179 180 double cx = envelope.getMin().getX(); 181 double cy = envelope.getMin().getY(); 182 double[] bb = new double[] { 0, 0, 0, 0 }; 183 int[] idx = getTileNumber( cx, cy, level ); 184 int xx = idx[0]; 185 int yy = idx[1]; 186 double minx = 9E99; 187 double miny = 9E99; 188 double maxx = -9E99; 189 double maxy = -9E99; 190 while ( cy < envelope.getMax().getY() + ( bb[3] - bb[1] ) ) { 191 List<String> row = new ArrayList<String>( 10 ); 192 while ( cx < envelope.getMax().getX() + ( bb[2] - bb[0] ) ) { 193 bb = getTileBBox( xx, yy, level ); 194 String s = props.getProperty( "TILEROOT" ) + level + "/" + xx + "/" + yy + extension; 195 xx++; 196 row.add( s ); 197 if ( bb[0] < minx ) { 198 minx = bb[0]; 199 } 200 if ( bb[1] < miny ) { 201 miny = bb[1]; 202 } 203 if ( bb[2] > maxx ) { 204 maxx = bb[2]; 205 } 206 if ( bb[3] > maxy ) { 207 maxy = bb[3]; 208 } 209 cx = cx + ( bb[2] - bb[0] ); 210 } 211 cx = envelope.getMin().getX(); 212 cy = cy + ( bb[3] - bb[1] ); 213 tiles.add( row ); 214 xx = idx[0]; 215 yy--; 216 } 217 218 target = new BufferedImage( tiles.get( 0 ).size() * tileSize, tiles.size() * tileSize, 219 BufferedImage.TYPE_INT_RGB ); 220 Graphics g = target.getGraphics(); 221 222 OSMReader[] threads = new OSMReader[tiles.size()]; 223 for ( int i = 0; i < tiles.size(); i++ ) { 224 List<String> row = tiles.get( i ); 225 threads[i] = new OSMReader( row, i, g ); 226 threads[i].start(); 227 } 228 while ( !isFinished( threads ) ) { 229 Thread.sleep( 50 ); 230 } 231 g.dispose(); 232 233 // calc result resolution in x- and y-direction ... 234 Point minP = (Point) gt2OSM.transform( GeometryFactory.createPoint( minx, miny, crsWGS84 ) ); 235 Point maxP = (Point) gt2OSM.transform( GeometryFactory.createPoint( maxx, maxy, crsWGS84 ) ); 236 double resx = ( maxP.getX() - minP.getX() ) / target.getWidth(); 237 double resy = ( maxP.getY() - minP.getY() ) / target.getHeight(); 238 // ... and rescale image to have same resolutions in both directions because otherwise 239 // deegree and other clients will not be able to render it correctly 240 if ( resx != resy ) { 241 target = scale( target, resx, resy ); 242 } 243 targetEnv = GeometryFactory.createEnvelope( minP.getPosition(), maxP.getPosition(), crsOSM ); 244 } 245 246 /** 247 * @param threads 248 * @return 249 */ 250 private boolean isFinished( OSMReader[] threads ) { 251 for ( OSMReader reader : threads ) { 252 if ( !reader.isFinished() ) { 253 return false; 254 } 255 } 256 return true; 257 } 258 259 /** 260 * @param target 261 * @param resx 262 * @param resy 263 * @return 264 */ 265 private BufferedImage scale( BufferedImage target, double resx, double resy ) { 266 Interpolation interpolation = new InterpolationBilinear(); 267 double scaleX = 1; 268 double scaleY = 1; 269 if ( resx < resy ) { 270 scaleY = resy / resx; 271 } else { 272 scaleX = resx / resy; 273 } 274 LOG.logDebug( "Scale image: by factors: " + scaleX + ' ' + scaleY ); 275 ParameterBlock pb = new ParameterBlock(); 276 pb.addSource( target ); 277 pb.add( (float) scaleX ); // The xScale 278 pb.add( (float) scaleY ); // The yScale 279 pb.add( 0.0F ); // The x translation 280 pb.add( 0.0F ); // The y translation 281 pb.add( interpolation ); // The interpolation 282 // Create the scale operation 283 RenderedOp ro = JAI.create( "scale", pb, null ); 284 try { 285 target = ro.getAsBufferedImage(); 286 } catch ( Exception e ) { 287 e.printStackTrace(); 288 } 289 return target; 290 } 291 292 private double[] getTileBBox( final int x, final int y, final int zoom ) { 293 double north = tile2lat( y, zoom ); 294 double south = tile2lat( y + 1, zoom ); 295 double west = tile2lon( x, zoom ); 296 double east = tile2lon( x + 1, zoom ); 297 return new double[] { west, south, east, north }; 298 } 299 300 private static double tile2lon( int x, int z ) { 301 return ( x / Math.pow( 2.0, z ) * 360.0 ) - 180; 302 } 303 304 private static double tile2lat( int y, int z ) { 305 double n = Math.PI - ( ( 2.0 * Math.PI * y ) / Math.pow( 2.0, z ) ); 306 return 180.0 / Math.PI * Math.atan( 0.5 * ( Math.exp( n ) - Math.exp( -n ) ) ); 307 } 308 309 /** 310 * @param scale 311 * @return required OSM slippy map level 312 */ 313 private int calculateLevel( double scale ) { 314 Iterator<double[]> range = levels.keySet().iterator(); 315 while ( range.hasNext() ) { 316 double[] ds = (double[]) range.next(); 317 if ( scale > ds[0] && scale <= ds[1] ) { 318 int l = levels.get( ds ); 319 if ( l < minLevel ) { 320 l = minLevel; 321 } else if ( l > maxLevel ) { 322 l = maxLevel; 323 } 324 return l; 325 } 326 } 327 return minLevel; 328 } 329 330 /* 331 * (non-Javadoc) 332 * 333 * @see 334 * org.deegree.ogcwebservices.wms.dataaccess.ExternalRasterDataAccess#perform(org.deegree.ogcwebservices.wms.operation 335 * .GetMap) 336 */ 337 public synchronized GetMapResult perform( GetMap getMap ) 338 throws Exception { 339 width = getMap.getWidth(); 340 height = getMap.getHeight(); 341 Envelope gmBBox = getMap.getBoundingBox(); 342 CoordinateSystem gmCRS = CRSFactory.create( getMap.getSrs() ); 343 if ( !gmCRS.equals( crsWGS84 ) ) { 344 envelope = gt2WGS84.transform( gmBBox, gmCRS, true ); 345 } else { 346 envelope = gmBBox; 347 } 348 // do the work 349 readSlippyMaps(); 350 351 // create a result map/image that matches the requested one 352 GeoTransformer gt = new GeoTransformer( gmCRS ); 353 target = gt.transform( target, targetEnv, gmBBox, width, height, 6, 3, 354 Interpolation.getInstance( Interpolation.INTERP_BILINEAR ) ); 355 return new GetMapResult( getMap, target ); 356 } 357 358 /* 359 * (non-Javadoc) 360 * 361 * @see 362 * org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess#perform(org.deegree.ogcwebservices.wms.operation 363 * .GetFeatureInfo) 364 */ 365 public GetFeatureInfoResult perform( GetFeatureInfo gfi ) { 366 // TODO Auto-generated method stub 367 return null; 368 } 369 370 /* 371 * (non-Javadoc) 372 * 373 * @see 374 * org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess#perform(org.deegree.ogcwebservices.wms.operation 375 * .GetLegendGraphic) 376 */ 377 public BufferedImage perform( GetLegendGraphic glg ) { 378 return legend; 379 } 380 381 /* 382 * (non-Javadoc) 383 * 384 * @see org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess#setConfigurationFile(java.net.URL) 385 */ 386 public void setConfigurationFile( URL url ) 387 throws IOException { 388 if ( url == null ) { 389 init(); 390 try { 391 BufferedImage tmp = ImageUtils.loadImage( new URL( props.getProperty( "LEGEND" ) ) ); 392 legend = new BufferedImage( tileSize, tileSize, BufferedImage.TYPE_INT_RGB ); 393 Graphics g = legend.getGraphics(); 394 g.drawImage( tmp, 0, 0, null ); 395 g.dispose(); 396 } catch ( IOException e ) { 397 LOG.logError( e.getMessage(), e ); 398 } 399 } else { 400 props = new Properties(); 401 InputStream is = url.openStream(); 402 props.load( is ); 403 is.close(); 404 if ( props.get( "TILEROOT" ) == null ) { 405 props.put( "TILEROOT", "http://tile.openstreetmap.org/" ); 406 } 407 if ( !props.getProperty( "TILEROOT" ).endsWith( "/" ) ) { 408 props.put( "TILEROOT", props.getProperty( "TILEROOT" ) + "/" ); 409 } 410 if ( props.getProperty( "TILEROOT" ).startsWith( "http://tah.openstreetmap.org" ) ) { 411 maxLevel = 17; 412 } else if ( props.getProperty( "TILEROOT" ).startsWith( "http://tah.openstreetmap.org" ) ) { 413 minLevel = 12; 414 maxLevel = 16; 415 } else if ( props.getProperty( "TILEROOT" ).startsWith( "http://richard.dev.openstreetmap.org" ) ) { 416 minLevel = 13; 417 maxLevel = 15; 418 extension = ".jpg"; 419 } 420 421 if ( props.get( "LEGEND" ) == null ) { 422 props.put( "LEGEND", "http://tile.openstreetmap.org/14/8514/5504.png" ); 423 } 424 XMLFragment dummy = new XMLFragment(); 425 dummy.setSystemId( url ); 426 URL legendURL = dummy.resolve( props.getProperty( "LEGEND" ) ); 427 try { 428 BufferedImage tmp = ImageUtils.loadImage( legendURL ); 429 legend = new BufferedImage( tmp.getWidth(), tmp.getHeight(), BufferedImage.TYPE_INT_RGB ); 430 Graphics g = legend.getGraphics(); 431 g.drawImage( tmp, 0, 0, null ); 432 g.dispose(); 433 return; 434 } catch ( IOException e ) { 435 LOG.logError( e.getMessage(), e ); 436 } 437 } 438 LOG.logDebug( "properties: ", props ); 439 440 } 441 442 // ////////////////////////////////////////////////////////////////////////////// 443 // inner classes 444 // ////////////////////////////////////////////////////////////////////////////// 445 446 private class OSMReader extends Thread { 447 448 private List<String> row; 449 450 private int y; 451 452 private Graphics g; 453 454 private boolean finished = false; 455 456 /** 457 * 458 * @param row 459 * @param y 460 */ 461 OSMReader( List<String> row, int y, Graphics g ) { 462 this.row = row; 463 this.y = y; 464 this.g = g; 465 } 466 467 /* 468 * (non-Javadoc) 469 * 470 * @see java.lang.Runnable#run() 471 */ 472 public void run() { 473 for ( int j = 0; j < row.size(); j++ ) { 474 BufferedImage tmp; 475 try { 476 tmp = ImageUtils.loadImage( new URL( row.get( j ) ) ); 477 } catch ( IOException e ) { 478 throw new RuntimeException( e ); 479 } 480 synchronized ( g ) { 481 g.drawImage( tmp, j * tileSize, target.getHeight() - ( y * tileSize ) - tileSize, null ); 482 } 483 } 484 finished = true; 485 } 486 487 /** 488 * @return the finished 489 */ 490 public boolean isFinished() { 491 return finished; 492 } 493 494 } 495 }