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