Package org.locationtech.udig.catalog.internal.wmt.wmtsource

Source Code of org.locationtech.udig.catalog.internal.wmt.wmtsource.WMTSource

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2010, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.catalog.internal.wmt.wmtsource;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.locationtech.udig.catalog.internal.wmt.Trace;
import org.locationtech.udig.catalog.internal.wmt.WMTPlugin;
import org.locationtech.udig.catalog.internal.wmt.WMTRenderJob;
import org.locationtech.udig.catalog.internal.wmt.WMTScaleZoomLevelMatcher;
import org.locationtech.udig.catalog.internal.wmt.WMTService;
import org.locationtech.udig.catalog.internal.wmt.tile.WMTTile;
import org.locationtech.udig.catalog.internal.wmt.tile.WMTTile.WMTTileFactory;
import org.locationtech.udig.catalog.internal.wmt.tile.WMTTile.WMTZoomLevel;
import org.locationtech.udig.catalog.internal.wmt.ui.properties.WMTLayerProperties;
import org.locationtech.udig.catalog.wmsc.server.Tile;
import org.locationtech.udig.core.internal.CorePlugin;

import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.util.ObjectCache;
import org.geotools.util.ObjectCaches;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Envelope;

/**
*
* @author to.srwn
* @since 1.1.0
*/
public abstract class WMTSource {  
   
    private String name;
   
    /**
     * This WeakHashMap acts as a memory cache.
     * Because we are using SoftReference, we won't run
     * out of Memory, the GC will free space.
     **/
    private ObjectCache tiles = ObjectCaches.create("soft", 50); //$NON-NLS-1$
   
    private WMTService wmtService;
   
    protected WMTSource() {}
   
    protected void init(String resourceId) throws Exception {}
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
   
    public String getId() {
        return getName();
    }
   
    public int getTileWidth() {
        return 256;
    }
   
    public int getTileHeight() {
        return 256;
    }
   
    public String getFileFormat() {
        return "png"; //$NON-NLS-1$
    }
   
    public ReferencedEnvelope getBounds() {
        return new ReferencedEnvelope(-180, 180, -85.051, 85.0511, DefaultGeographicCRS.WGS84);       
    }
   
    //region CRS
    public static final CoordinateReferenceSystem CRS_EPSG_900913;   
    static {
        CoordinateReferenceSystem crs = null;
       
        try {
            crs = CRS.decode("EPSG:900913"); //$NON-NLS-1$
        } catch (Exception exc1) {
            WMTPlugin.trace("[WMTSource] EPSG:900913 is not in the database, now it is created manually", null); //$NON-NLS-1$
           
            String wkt =
                "PROJCS[\"Google Mercator\"," + //$NON-NLS-1$
               "GEOGCS[\"WGS 84\"," + //$NON-NLS-1$
                "    DATUM[\"World Geodetic System 1984\"," + //$NON-NLS-1$
                "        SPHEROID[\"WGS 84\",6378137.0,298.257223563," + //$NON-NLS-1$
                "            AUTHORITY[\"EPSG\",\"7030\"]]," + //$NON-NLS-1$
                "        AUTHORITY[\"EPSG\",\"6326\"]]," + //$NON-NLS-1$
                "    PRIMEM[\"Greenwich\",0.0," + //$NON-NLS-1$
                "        AUTHORITY[\"EPSG\",\"8901\"]]," + //$NON-NLS-1$
                "    UNIT[\"degree\",0.017453292519943295]," + //$NON-NLS-1$
                "    AXIS[\"Geodetic latitude\",NORTH]," + //$NON-NLS-1$
                "    AXIS[\"Geodetic longitude\",EAST]," + //$NON-NLS-1$
                "    AUTHORITY[\"EPSG\",\"4326\"]]," + //$NON-NLS-1$
                "PROJECTION[\"Mercator_1SP\"]," + //$NON-NLS-1$
                "PARAMETER[\"semi_minor\",6378137.0]," + //$NON-NLS-1$
                "PARAMETER[\"latitude_of_origin\",0.0]," + //$NON-NLS-1$
                "PARAMETER[\"central_meridian\",0.0]," + //$NON-NLS-1$
                "PARAMETER[\"scale_factor\",1.0],"+ //$NON-NLS-1$
                "PARAMETER[\"false_easting\",0.0]," + //$NON-NLS-1$
                "PARAMETER[\"false_northing\",0.0]," + //$NON-NLS-1$
                "UNIT[\"m\",1.0]," + //$NON-NLS-1$
                "AXIS[\"Easting\",EAST]," + //$NON-NLS-1$
                "AXIS[\"Northing\",NORTH]," + //$NON-NLS-1$
                "AUTHORITY[\"EPSG\",\"900913\"]]"; //$NON-NLS-1$
           
            try {
                crs = CRS.parseWKT(wkt);
            } catch (Exception exc2) {
                WMTPlugin.log("[WMTSource] Could not build EPSG:900913!", exc2); //$NON-NLS-1$
                crs = DefaultGeographicCRS.WGS84;
            }
        }
       
        CRS_EPSG_900913 = crs;
    }
   
    /**
     * The projection the tiles are drawn in.
     *
     * @return
     */
    public CoordinateReferenceSystem getProjectedTileCrs() {
        return WMTSource.CRS_EPSG_900913;
    }
   
    /**
     * The CRS that is used when the extent is cut in tiles.
     *
     * @return
     */
    public CoordinateReferenceSystem getTileCrs() {
        return DefaultGeographicCRS.WGS84;
    }
    //endregion

    //region Methods to access the tile-list (cache)
    public boolean listContainsTile(String tileId) {
        return !(tiles.peek(tileId) == null || tiles.get(tileId) == null);
    }
   
    public WMTTile addTileToList(WMTTile tile) {
        if (listContainsTile(tile.getId())){
            WMTPlugin.debug("[WMTSource.addTileToList] Already in cache: " + tile.getId(), Trace.REQUEST); //$NON-NLS-1$
            return getTileFromList(tile.getId());
        } else {
            WMTPlugin.debug("[WMTSource.addTileToList] Was not in cache: " + tile.getId(), Trace.REQUEST); //$NON-NLS-1$
            tiles.put(tile.getId(), tile);
            return tile;           
        }
    }
   
    public WMTTile getTileFromList(String tileId) {
        return (WMTTile) tiles.get(tileId);
    }
    //endregion
   
    //region Methods related to the service   
    public WMTService getWmtService() {
        return wmtService;
    }

    public void setWmtService(WMTService wmtService) {
        this.wmtService = wmtService;
    }
   
    /**
     * Returns the catalog url for a given class.
     * <pre>
     * For example:
     * getRelatedServiceUrl(OSMMapnikSource.class) returns:
     * wmt://localhost/wmt/org.locationtech.udig.catalog.internal.wmt.wmtsource.OSMMapnikSource
     * </pre>
     *
     * @param sourceClass
     * @return catalog url
     */
    public static URL getRelatedServiceUrl(Class<? extends WMTSource> sourceClass) {
        URL url;
       
        try {
            url = new URL(null, WMTService.ID + sourceClass.getName(), CorePlugin.RELAXED_HANDLER);
        }
        catch(MalformedURLException exc) {
            WMTPlugin.log("[WMTSource.getRelatedServiceUrl] Could not create url: " + sourceClass.getName(), exc); //$NON-NLS-1$
            url = null;
        }       
       
        return url;
    }
   
    /**
     * Generates the URL for CloudMadeSource-Services
     *
     * @param styleId
     * @return
     */
    public static URL getCloudMadeServiceUrl(String styleId) {
        URL url = getRelatedServiceUrl(OSMCloudMadeSource.class);
       
        try {
            url = new URL(null, url.toExternalForm() + "/" + styleId, CorePlugin.RELAXED_HANDLER); //$NON-NLS-1$
        }
        catch(MalformedURLException exc) {
            WMTPlugin.log("[WMTSource.getCloudMadeServiceUrl] Could not create url: " + styleId, exc); //$NON-NLS-1$
            url = null;
        }       
       
        return url;
    }
   
    /**
     * Generates the URL for CustomServer-Services
     *
     * @param serverUrl
     * @param zoomMin
     * @param zoomMax
     * @param type if !=null it contains the tiles schema different from google's.
     * @return
     */
    public static URL getCustomServerServiceUrl(String serverUrl, String zoomMin, String zoomMax, String type) {
        URL url = getRelatedServiceUrl(CSSource.class);
       
        try {
            StringBuilder sb = new StringBuilder();
            sb.append(url.toExternalForm());
            sb.append("/");
            sb.append(serverUrl);
            sb.append("/");
            sb.append(zoomMin);
            sb.append("/");
            sb.append(zoomMax);
            if (type!=null) {
                sb.append("/");
                sb.append(type);
            }
            url = new URL(null,
                    sb.toString(),
                    CorePlugin.RELAXED_HANDLER);
        }
        catch(MalformedURLException exc) {
            WMTPlugin.log("[WMTSource.getCustomServerServiceUrl] Could not create url: " + serverUrl + " " + zoomMin + " " + zoomMax, exc); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            url = null;
        }       
       
        return url;
    }
    //endregion
   
    //region Zoom-level
    /**
     * Returns a list that represents a mapping between zoom-levels and map scale.
     *
     * Array index: zoom-level
     * Value at index: map scale
     *
     * High zoom-level -> more detailed map
     * Low zoom-level -> less detailed map
     *
     * @return mapping between zoom-levels and map scale
     */
    public abstract double[] getScaleList();
   
    /**
     * Translates the map scale into a zoom-level for the map services.
     *
     * The scale-factor (0-100) decides whether the tiles will be
     * scaled down (100) or scaled up (0).
     *
     * @param renderJob Contains all the needed information
     * @param scaleFactor Scale-factor (0-100)
     * @return Zoom-level
     */
    public int getZoomLevelFromMapScale(WMTScaleZoomLevelMatcher zoomLevelMatcher, int scaleFactor) {
        // fallback scale-list
        double[] scaleList = getScaleList();
        // during the calculations this list caches already calculated scales
        double[] tempScaleList = new double[scaleList.length];
        Arrays.fill(tempScaleList, Double.NaN);
       
        assert(scaleList != null && scaleList.length > 0);
       
        int zoomLevel = zoomLevelMatcher.getZoomLevelFromScale(this, tempScaleList);
       
        // Now apply the scale-factor
        if (zoomLevel == 0) {
            return zoomLevel;
        } else {
            int upperScaleIndex = zoomLevel - 1;
            int lowerScaleIndex = zoomLevel;
           
            double deltaScale = tempScaleList[upperScaleIndex] - tempScaleList[lowerScaleIndex];
            double rangeScale = (scaleFactor / 100d) * deltaScale;
            double limitScale = tempScaleList[lowerScaleIndex] + rangeScale;
           
            if (zoomLevelMatcher.getScale() > limitScale) {
                return upperScaleIndex;
            } else {
                return lowerScaleIndex;
            }
        }
    }
   
    /**
     * Returns the zoom-level that should be used to fetch the tiles.
     *
     * @param scale
     * @param scaleFactor
     * @param useRecommended always use the calculated zoom-level, do not use the one the user selected
     * @return
     */
    public int getZoomLevelToUse(WMTScaleZoomLevelMatcher zoomLevelMatcher, int scaleFactor, boolean useRecommended,
            WMTLayerProperties layerProperties) {
        if (useRecommended) {
            return getZoomLevelFromMapScale(zoomLevelMatcher, scaleFactor);           
        }
       
        // try to load the property values
        boolean selectionAutomatic = true;
        int zoomLevel = -1;
       
        if (layerProperties.load()) {           
            selectionAutomatic = layerProperties.getSelectionAutomatic();
            zoomLevel = layerProperties.getZoomLevel();
        } else {
            selectionAutomatic = true;
        }
       
        // check if the zoom-level is valid
        if (!selectionAutomatic &&
                ((zoomLevel >= getMinZoomLevel()) && (zoomLevel <= getMaxZoomLevel()))) {
            // the zoom-level from the properties is valid, so let's take it
            return zoomLevel;
        } else {
            // No valid property values or automatic selection of the zoom-level
            return getZoomLevelFromMapScale(zoomLevelMatcher, scaleFactor);
        }
    }
   
    /**
     * Returns the lowest zoom-level number from the scaleList.
     *
     * @param scaleList
     * @return
     */
    public int getMinZoomLevel() {
        double[] scaleList = getScaleList();
        int minZoomLevel = 0;
       
        while (Double.isNaN(scaleList[minZoomLevel]) && (minZoomLevel < scaleList.length)) {
            minZoomLevel++;
        }
       
        return minZoomLevel;      
    }
   
    /**
     * Returns the highest zoom-level number from the scaleList.
     *
     * @param scaleList
     * @return
     */
    public int getMaxZoomLevel() {
        double[] scaleList = getScaleList();
        int maxZoomLevel = scaleList.length - 1;
       
        while (Double.isNaN(scaleList[maxZoomLevel]) && (maxZoomLevel >= 0)) {
            maxZoomLevel--;
        }
       
        return maxZoomLevel;
    }
    //endregion
   
    //region Tiles-Cutting 
    /**
     * Returns the TileFactory which is used to call the
     * method getTileFromCoordinate().
     */
    public abstract WMTTileFactory getTileFactory();
  
    /**
     * The method which finds all tiles that are within the given extent,
     * used for all different map services.
     *
     * @see WMTSource.cutExtentIntoTiles(ReferencedEnvelope extent, double scale)
     * @param extent The extent which should be cut.
     * @param scale The map scale.
     * @param scaleFactor The scale-factor (0-100): scale up or down?
     * @param recommendedZoomLevel Always use the calculated zoom-level, do not use the one the user selected
     * @param tileLimitWarning
     * @return The list of found tiles.
     */
    public Map<String, Tile> cutExtentIntoTiles(WMTRenderJob renderJob,
            int scaleFactor, boolean recommendedZoomLevel, WMTLayerProperties layerProperties,
            int tileLimitWarning) {       
        // only continue, if we have tiles that cover the requested extent
        if (!renderJob.getMapExtentTileCrs().intersects((Envelope) getBounds())) {
            return Collections.emptyMap();
        }
       
        ReferencedEnvelope extent = normalizeExtent(renderJob.getMapExtentTileCrs());
       
        WMTTileFactory tileFactory = getTileFactory();
               
        WMTZoomLevel zoomLevel = tileFactory.getZoomLevel(getZoomLevelToUse(renderJob.getZoomLevelMatcher(),
                scaleFactor, recommendedZoomLevel, layerProperties), this);
        long maxNumberOfTiles = zoomLevel.getMaxTileNumber();
               
        Map<String, Tile> tileList = new HashMap<String, Tile>();
       
        WMTPlugin.debug("[WMTSource.cutExtentIntoTiles] Zoom-Level: " + zoomLevel.getZoomLevel() //$NON-NLS-1$
                " Extent: " + extent, Trace.REQUEST); //$NON-NLS-1$
       
        // Let's get the first tile which covers the upper-left corner
        WMTTile firstTile = tileFactory.getTileFromCoordinate(
                extent.getMaxY(), extent.getMinX(), zoomLevel, this);
        tileList.put(firstTile.getId(), addTileToList(firstTile));
       
        WMTTile firstTileOfRow = null;
        WMTTile movingTile = firstTileOfRow = firstTile;
        // Loop column
        do {
            // Loop row
            do {
                // get the next tile right of this one
                WMTTile rightNeighbour = movingTile.getRightNeighbour();
               
                // Check if the new tile is still part of the extent and
                // that we don't have the first tile again
                if (extent.intersects((Envelope) rightNeighbour.getExtent())
                        && !firstTileOfRow.equals(rightNeighbour)) {
                    tileList.put(rightNeighbour.getId(), addTileToList(rightNeighbour));
                   
                    WMTPlugin.debug("[WMTSource.cutExtentIntoTiles] Adding right neighbour: " //$NON-NLS-1$
                            rightNeighbour.getId(), Trace.REQUEST);
                   
                    movingTile = rightNeighbour;
                } else {
                    break;
                }
                if (tileList.size()>tileLimitWarning) {
                    return Collections.emptyMap();
                }
            } while(tileList.size() < maxNumberOfTiles);

            // get the next tile under the first one of the row
            WMTTile lowerNeighbour = firstTileOfRow.getLowerNeighbour();
           
            // Check if the new tile is still part of the extent
            if (extent.intersects((Envelope) lowerNeighbour.getExtent())
                    && !firstTile.equals(lowerNeighbour)) {
                tileList.put(lowerNeighbour.getId(), addTileToList(lowerNeighbour));
               
                WMTPlugin.debug("[WMTSource.cutExtentIntoTiles] Adding lower neighbour: " //$NON-NLS-1$
                        lowerNeighbour.getId(), Trace.REQUEST);
               
                firstTileOfRow = movingTile = lowerNeighbour;
            } else {
                break;
            }           
        } while(tileList.size() < maxNumberOfTiles);
       
        return tileList;
    }
   
    /**
     * The extent from the viewport may look like this:
     * MaxY: 110° (=-70°)   MinY: -110°
     * MaxX: 180°           MinX: -180°
     *
     * But cutExtentIntoTiles(..) requires an extent that looks like this:
     * MaxY: 85° (or 90°)   MinY: -85° (or -90°)
     * MaxX: 180°           MinX: -180°
     *
     * @param envelope
     * @return
     */
    private ReferencedEnvelope normalizeExtent(ReferencedEnvelope envelope) {
        ReferencedEnvelope bounds = getBounds();
       
        if (    envelope.getMaxY() > bounds.getMaxY() ||
                envelope.getMinY() < bounds.getMinY() ||
                envelope.getMaxX() > bounds.getMaxX() ||
                envelope.getMinX() < bounds.getMinX()   ) {
           
           
            double maxY = (envelope.getMaxY() > bounds.getMaxY()) ? bounds.getMaxY() : envelope.getMaxY();
            double minY = (envelope.getMinY() < bounds.getMinY()) ? bounds.getMinY() : envelope.getMinY();
            double maxX = (envelope.getMaxX() > bounds.getMaxX()) ? bounds.getMaxX() : envelope.getMaxX();
            double minX = (envelope.getMinX() < bounds.getMinX()) ? bounds.getMinX() : envelope.getMinX();
           
            ReferencedEnvelope newEnvelope = new ReferencedEnvelope(minX, maxX, minY, maxY,
                    envelope.getCoordinateReferenceSystem());
           
            return newEnvelope;
        }
       
        return envelope;
    }
    //endregion
   
    @Override
    public String toString() {
        return getName();
    }
}
TOP

Related Classes of org.locationtech.udig.catalog.internal.wmt.wmtsource.WMTSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.