001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }