Changeset 10990 in josm for trunk/src


Ignore:
Timestamp:
2016-09-10T21:27:19+02:00 (8 years ago)
Author:
wiktorn
Message:

Small refactor of WMS TileSource

  • Introduce AbstractWMSTileSource, which will be a base class for WMSEndpointTileSource
  • remove getHeaders() from WMSLayer, super method is already good enough
Location:
trunk/src/org/openstreetmap/josm
Files:
2 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java

    r10989 r10990  
    22package org.openstreetmap.josm.data.imagery;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    5 
    64import java.awt.Point;
    7 import java.text.DecimalFormat;
    8 import java.text.DecimalFormatSymbols;
    9 import java.text.NumberFormat;
    10 import java.util.Locale;
    11 import java.util.Map;
    12 import java.util.Set;
    13 import java.util.TreeSet;
    14 import java.util.concurrent.ConcurrentHashMap;
    15 import java.util.regex.Matcher;
    16 import java.util.regex.Pattern;
    175
    186import org.openstreetmap.gui.jmapviewer.Tile;
    197import org.openstreetmap.gui.jmapviewer.TileXY;
    208import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
    21 import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
    229import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
     10import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
    2311import org.openstreetmap.josm.Main;
    2412import org.openstreetmap.josm.data.Bounds;
     
    2715import org.openstreetmap.josm.data.coor.LatLon;
    2816import org.openstreetmap.josm.data.projection.Projection;
    29 import org.openstreetmap.josm.gui.layer.WMSLayer;
    30 import org.openstreetmap.josm.tools.CheckParameterUtil;
    3117
    3218/**
    33  * Tile Source handling WMS providers
     19 * Base class for different WMS tile sources those based on URL templates and those based on WMS endpoints
     20 * @author Wiktor Niesiobędzki
     21 * @since 10990
    3422 *
    35  * @author Wiktor Niesiobędzki
    36  * @since 8526
    3723 */
    38 public class TemplatedWMSTileSource extends TMSTileSource implements TemplatedTileSource {
    39     private final Map<String, String> headers = new ConcurrentHashMap<>();
    40     private final Set<String> serverProjections;
     24public class AbstractWMSTileSource extends TMSTileSource {
     25
    4126    private EastNorth anchorPosition;
    4227    private int[] tileXMin;
     
    4530    private int[] tileYMax;
    4631    private double[] degreesPerTile;
    47 
    48     // CHECKSTYLE.OFF: SingleSpaceSeparator
    49     private static final Pattern PATTERN_HEADER = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
    50     private static final Pattern PATTERN_PROJ   = Pattern.compile("\\{proj\\}");
    51     private static final Pattern PATTERN_WKID   = Pattern.compile("\\{wkid\\}");
    52     private static final Pattern PATTERN_BBOX   = Pattern.compile("\\{bbox\\}");
    53     private static final Pattern PATTERN_W      = Pattern.compile("\\{w\\}");
    54     private static final Pattern PATTERN_S      = Pattern.compile("\\{s\\}");
    55     private static final Pattern PATTERN_E      = Pattern.compile("\\{e\\}");
    56     private static final Pattern PATTERN_N      = Pattern.compile("\\{n\\}");
    57     private static final Pattern PATTERN_WIDTH  = Pattern.compile("\\{width\\}");
    58     private static final Pattern PATTERN_HEIGHT = Pattern.compile("\\{height\\}");
    59     private static final Pattern PATTERN_PARAM  = Pattern.compile("\\{([^}]+)\\}");
    60     // CHECKSTYLE.ON: SingleSpaceSeparator
    61 
    62     private static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));
    63 
    64     private static final Pattern[] ALL_PATTERNS = {
    65         PATTERN_HEADER, PATTERN_PROJ, PATTERN_WKID, PATTERN_BBOX, PATTERN_W, PATTERN_S, PATTERN_E, PATTERN_N, PATTERN_WIDTH, PATTERN_HEIGHT
    66     };
    67 
    68     /*
    69      * Constant taken from OGC WMTS Implementation Specification (http://www.opengeospatial.org/standards/wmts)
    70      * From table E.4 - Definition of Well-known scale set GoogleMapsCompatibile
    71      *
    72      *  As higher zoom levels have denominator divided by 2, we keep only zoom level 1 in the code
    73      */
    7432    private static final float SCALE_DENOMINATOR_ZOOM_LEVEL_1 = 559082264.0287178f;
    7533
    76     /**
    77      * Creates a tile source based on imagery info
    78      * @param info imagery info
    79      */
    80     public TemplatedWMSTileSource(ImageryInfo info) {
     34    public AbstractWMSTileSource(TileSourceInfo info) {
    8135        super(info);
    82         this.serverProjections = new TreeSet<>(info.getServerProjections());
    83         handleTemplate();
    84         initProjection();
    8536    }
    8637
     
    12980            tileYMax[zoom] = maxTileIndex.getYIndex();
    13081        }
    131     }
    132 
    133     @Override
    134     public int getDefaultTileSize() {
    135         return WMSLayer.PROP_IMAGE_SIZE.get();
    136     }
    137 
    138     @Override
    139     public String getTileUrl(int zoom, int tilex, int tiley) {
    140         String myProjCode = Main.getProjection().toCode();
    141 
    142         EastNorth nw = getTileEastNorth(tilex, tiley, zoom);
    143         EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom);
    144 
    145         double w = nw.getX();
    146         double n = nw.getY();
    147 
    148         double s = se.getY();
    149         double e = se.getX();
    150 
    151         if (!serverProjections.contains(myProjCode) && serverProjections.contains("EPSG:4326") && "EPSG:3857".equals(myProjCode)) {
    152             LatLon swll = Main.getProjection().eastNorth2latlon(new EastNorth(w, s));
    153             LatLon nell = Main.getProjection().eastNorth2latlon(new EastNorth(e, n));
    154             myProjCode = "EPSG:4326";
    155             s = swll.lat();
    156             w = swll.lon();
    157             n = nell.lat();
    158             e = nell.lon();
    159         }
    160 
    161         if ("EPSG:4326".equals(myProjCode) && !serverProjections.contains(myProjCode) && serverProjections.contains("CRS:84")) {
    162             myProjCode = "CRS:84";
    163         }
    164 
    165         // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326.
    166         //
    167         // Background:
    168         //
    169         // bbox=x_min,y_min,x_max,y_max
    170         //
    171         //      SRS=... is WMS 1.1.1
    172         //      CRS=... is WMS 1.3.0
    173         //
    174         // The difference:
    175         //      For SRS x is east-west and y is north-south
    176         //      For CRS x and y are as specified by the EPSG
    177         //          E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326.
    178         //          For most other EPSG code there seems to be no difference.
    179         // CHECKSTYLE.OFF: LineLength
    180         // [1] https://www.epsg-registry.org/report.htm?type=selection&entity=urn:ogc:def:crs:EPSG::4326&reportDetail=short&style=urn:uuid:report-style:default-with-code&style_name=OGP%20Default%20With%20Code&title=EPSG:4326
    181         // CHECKSTYLE.ON: LineLength
    182         boolean switchLatLon = false;
    183         if (baseUrl.toLowerCase(Locale.US).contains("crs=epsg:4326")) {
    184             switchLatLon = true;
    185         } else if (baseUrl.toLowerCase(Locale.US).contains("crs=")) {
    186             // assume WMS 1.3.0
    187             switchLatLon = Main.getProjection().switchXY();
    188         }
    189         String bbox;
    190         if (switchLatLon) {
    191             bbox = String.format("%s,%s,%s,%s", latLonFormat.format(s), latLonFormat.format(w), latLonFormat.format(n), latLonFormat.format(e));
    192         } else {
    193             bbox = String.format("%s,%s,%s,%s", latLonFormat.format(w), latLonFormat.format(s), latLonFormat.format(e), latLonFormat.format(n));
    194         }
    195 
    196         // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll
    197         StringBuffer url = new StringBuffer(baseUrl.length());
    198         Matcher matcher = PATTERN_PARAM.matcher(baseUrl);
    199         while (matcher.find()) {
    200             String replacement;
    201             switch (matcher.group(1)) {
    202             case "proj":
    203                 replacement = myProjCode;
    204                 break;
    205             case "wkid":
    206                 replacement = myProjCode.startsWith("EPSG:") ? myProjCode.substring(5) : myProjCode;
    207                 break;
    208             case "bbox":
    209                 replacement = bbox;
    210                 break;
    211             case "w":
    212                 replacement = latLonFormat.format(w);
    213                 break;
    214             case "s":
    215                 replacement = latLonFormat.format(s);
    216                 break;
    217             case "e":
    218                 replacement = latLonFormat.format(e);
    219                 break;
    220             case "n":
    221                 replacement = latLonFormat.format(n);
    222                 break;
    223             case "width":
    224             case "height":
    225                 replacement = String.valueOf(getTileSize());
    226                 break;
    227             default:
    228                 replacement = '{' + matcher.group(1) + '}';
    229             }
    230             matcher.appendReplacement(url, replacement);
    231         }
    232         matcher.appendTail(url);
    233         return url.toString().replace(" ", "%20");
    234     }
    235 
    236     @Override
    237     public String getTileId(int zoom, int tilex, int tiley) {
    238         return getTileUrl(zoom, tilex, tiley);
    23982    }
    24083
     
    299142        EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
    300143        return new Point(
    301                     (int) Math.round((point.east() - anchorPosition.east()) / scale),
    302                     (int) Math.round((anchorPosition.north() - point.north()) / scale)
     144                (int) Math.round((point.east() - anchorPosition.east()) / scale),
     145                (int) Math.round((anchorPosition.north() - point.north()) / scale)
    303146                );
    304147    }
     
    325168    }
    326169
    327     @Override
    328     public Map<String, String> getHeaders() {
    329         return headers;
    330     }
    331 
    332     /**
    333      * Checks if url is acceptable by this Tile Source
    334      * @param url URL to check
    335      */
    336     public static void checkUrl(String url) {
    337         CheckParameterUtil.ensureParameterNotNull(url, "url");
    338         Matcher m = PATTERN_PARAM.matcher(url);
    339         while (m.find()) {
    340             boolean isSupportedPattern = false;
    341             for (Pattern pattern : ALL_PATTERNS) {
    342                 if (pattern.matcher(m.group()).matches()) {
    343                     isSupportedPattern = true;
    344                     break;
    345                 }
    346             }
    347             if (!isSupportedPattern) {
    348                 throw new IllegalArgumentException(
    349                         tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url));
    350             }
    351         }
    352     }
    353 
    354     private void handleTemplate() {
    355         // Capturing group pattern on switch values
    356         StringBuffer output = new StringBuffer();
    357         Matcher matcher = PATTERN_HEADER.matcher(this.baseUrl);
    358         while (matcher.find()) {
    359             headers.put(matcher.group(1), matcher.group(2));
    360             matcher.appendReplacement(output, "");
    361         }
    362         matcher.appendTail(output);
    363         this.baseUrl = output.toString();
    364     }
    365 
    366170    protected EastNorth getTileEastNorth(int x, int y, int z) {
    367171        double scale = getDegreesPerTile(z);
    368172        return new EastNorth(
    369                         anchorPosition.east() + x * scale,
    370                         anchorPosition.north() - y * scale
    371                         );
     173                anchorPosition.east() + x * scale,
     174                anchorPosition.north() - y * scale
     175                );
    372176    }
    373177
     
    375179        return degreesPerTile[zoom];
    376180    }
     181
    377182}
  • trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java

    r10378 r10990  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.Point;
    76import java.text.DecimalFormat;
    87import java.text.DecimalFormatSymbols;
     
    1615import java.util.regex.Pattern;
    1716
    18 import org.openstreetmap.gui.jmapviewer.Tile;
    19 import org.openstreetmap.gui.jmapviewer.TileXY;
    20 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
    2117import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
    22 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
    2318import org.openstreetmap.josm.Main;
    24 import org.openstreetmap.josm.data.Bounds;
    25 import org.openstreetmap.josm.data.ProjectionBounds;
    2619import org.openstreetmap.josm.data.coor.EastNorth;
    2720import org.openstreetmap.josm.data.coor.LatLon;
    28 import org.openstreetmap.josm.data.projection.Projection;
    2921import org.openstreetmap.josm.gui.layer.WMSLayer;
    3022import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    3628 * @since 8526
    3729 */
    38 public class TemplatedWMSTileSource extends TMSTileSource implements TemplatedTileSource {
     30public class TemplatedWMSTileSource extends AbstractWMSTileSource implements TemplatedTileSource {
    3931    private final Map<String, String> headers = new ConcurrentHashMap<>();
    4032    private final Set<String> serverProjections;
    41     private EastNorth anchorPosition;
    42     private int[] tileXMin;
    43     private int[] tileYMin;
    44     private int[] tileXMax;
    45     private int[] tileYMax;
    46     private double[] degreesPerTile;
    47 
    4833    // CHECKSTYLE.OFF: SingleSpaceSeparator
    4934    private static final Pattern PATTERN_HEADER = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
     
    6651    };
    6752
    68     /*
    69      * Constant taken from OGC WMTS Implementation Specification (http://www.opengeospatial.org/standards/wmts)
    70      * From table E.4 - Definition of Well-known scale set GoogleMapsCompatibile
    71      *
    72      *  As higher zoom levels have denominator divided by 2, we keep only zoom level 1 in the code
    73      */
    74     private static final float SCALE_DENOMINATOR_ZOOM_LEVEL_1 = 559082264.0287178f;
    75 
    7653    /**
    7754     * Creates a tile source based on imagery info
     
    8360        handleTemplate();
    8461        initProjection();
    85     }
    86 
    87     /**
    88      * Initializes class with current projection in JOSM. This call is needed every time projection changes.
    89      */
    90     public void initProjection() {
    91         initProjection(Main.getProjection());
    92     }
    93 
    94     private void initAnchorPosition(Projection proj) {
    95         Bounds worldBounds = proj.getWorldBoundsLatLon();
    96         EastNorth min = proj.latlon2eastNorth(worldBounds.getMin());
    97         EastNorth max = proj.latlon2eastNorth(worldBounds.getMax());
    98         this.anchorPosition = new EastNorth(min.east(), max.north());
    99     }
    100 
    101     /**
    102      * Initializes class with projection in JOSM. This call is needed every time projection changes.
    103      * @param proj new projection that shall be used for computations
    104      */
    105     public void initProjection(Projection proj) {
    106         initAnchorPosition(proj);
    107         ProjectionBounds worldBounds = proj.getWorldBoundsBoxEastNorth();
    108 
    109         EastNorth topLeft = new EastNorth(worldBounds.getMin().east(), worldBounds.getMax().north());
    110         EastNorth bottomRight = new EastNorth(worldBounds.getMax().east(), worldBounds.getMin().north());
    111 
    112         // use 256 as "tile size" to keep the scale in line with default tiles in Mercator projection
    113         double crsScale = 256 * 0.28e-03 / proj.getMetersPerUnit();
    114         tileXMin = new int[getMaxZoom() + 1];
    115         tileYMin = new int[getMaxZoom() + 1];
    116         tileXMax = new int[getMaxZoom() + 1];
    117         tileYMax = new int[getMaxZoom() + 1];
    118         degreesPerTile = new double[getMaxZoom() + 1];
    119 
    120         for (int zoom = 1; zoom <= getMaxZoom(); zoom++) {
    121             // use well known scale set "GoogleCompatibile" from OGC WMTS spec to calculate number of tiles per zoom level
    122             // this makes the zoom levels "glued" to standard TMS zoom levels
    123             degreesPerTile[zoom] = (SCALE_DENOMINATOR_ZOOM_LEVEL_1 / Math.pow(2d, zoom - 1d)) * crsScale;
    124             TileXY minTileIndex = eastNorthToTileXY(topLeft, zoom);
    125             tileXMin[zoom] = minTileIndex.getXIndex();
    126             tileYMin[zoom] = minTileIndex.getYIndex();
    127             TileXY maxTileIndex = eastNorthToTileXY(bottomRight, zoom);
    128             tileXMax[zoom] = maxTileIndex.getXIndex();
    129             tileYMax[zoom] = maxTileIndex.getYIndex();
    130         }
    13162    }
    13263
     
    237168    public String getTileId(int zoom, int tilex, int tiley) {
    238169        return getTileUrl(zoom, tilex, tiley);
    239     }
    240 
    241     @Override
    242     public ICoordinate tileXYToLatLon(Tile tile) {
    243         return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());
    244     }
    245 
    246     @Override
    247     public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {
    248         return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);
    249     }
    250 
    251     @Override
    252     public ICoordinate tileXYToLatLon(int x, int y, int zoom) {
    253         return Main.getProjection().eastNorth2latlon(getTileEastNorth(x, y, zoom)).toCoordinate();
    254     }
    255 
    256     @Override
    257     public TileXY latLonToTileXY(double lat, double lon, int zoom) {
    258         Projection proj = Main.getProjection();
    259         EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon));
    260         return eastNorthToTileXY(enPoint, zoom);
    261     }
    262 
    263     private TileXY eastNorthToTileXY(EastNorth enPoint, int zoom) {
    264         double scale = getDegreesPerTile(zoom);
    265         return new TileXY(
    266                 (enPoint.east() - anchorPosition.east()) / scale,
    267                 (anchorPosition.north() - enPoint.north()) / scale
    268                 );
    269     }
    270 
    271     @Override
    272     public TileXY latLonToTileXY(ICoordinate point, int zoom) {
    273         return latLonToTileXY(point.getLat(), point.getLon(), zoom);
    274     }
    275 
    276     @Override
    277     public int getTileXMax(int zoom) {
    278         return tileXMax[zoom];
    279     }
    280 
    281     @Override
    282     public int getTileXMin(int zoom) {
    283         return tileXMin[zoom];
    284     }
    285 
    286     @Override
    287     public int getTileYMax(int zoom) {
    288         return tileYMax[zoom];
    289     }
    290 
    291     @Override
    292     public int getTileYMin(int zoom) {
    293         return tileYMin[zoom];
    294     }
    295 
    296     @Override
    297     public Point latLonToXY(double lat, double lon, int zoom) {
    298         double scale = getDegreesPerTile(zoom) / getTileSize();
    299         EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
    300         return new Point(
    301                     (int) Math.round((point.east() - anchorPosition.east()) / scale),
    302                     (int) Math.round((anchorPosition.north() - point.north()) / scale)
    303                 );
    304     }
    305 
    306     @Override
    307     public Point latLonToXY(ICoordinate point, int zoom) {
    308         return latLonToXY(point.getLat(), point.getLon(), zoom);
    309     }
    310 
    311     @Override
    312     public ICoordinate xyToLatLon(Point point, int zoom) {
    313         return xyToLatLon(point.x, point.y, zoom);
    314     }
    315 
    316     @Override
    317     public ICoordinate xyToLatLon(int x, int y, int zoom) {
    318         double scale = getDegreesPerTile(zoom) / getTileSize();
    319         Projection proj = Main.getProjection();
    320         EastNorth ret = new EastNorth(
    321                 anchorPosition.east() + x * scale,
    322                 anchorPosition.north() - y * scale
    323                 );
    324         return proj.eastNorth2latlon(ret).toCoordinate();
    325170    }
    326171
     
    363208        this.baseUrl = output.toString();
    364209    }
    365 
    366     protected EastNorth getTileEastNorth(int x, int y, int z) {
    367         double scale = getDegreesPerTile(z);
    368         return new EastNorth(
    369                         anchorPosition.east() + x * scale,
    370                         anchorPosition.north() - y * scale
    371                         );
    372     }
    373 
    374     private double getDegreesPerTile(int zoom) {
    375         return degreesPerTile[zoom];
    376     }
    377210}
  • trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java

    r10568 r10990  
    88import java.util.Arrays;
    99import java.util.List;
    10 import java.util.Map;
    1110import java.util.Set;
    1211import java.util.TreeSet;
     
    2019import org.openstreetmap.josm.Main;
    2120import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
     21import org.openstreetmap.josm.data.imagery.AbstractWMSTileSource;
    2222import org.openstreetmap.josm.data.imagery.ImageryInfo;
    2323import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
     
    3636 *
    3737 */
    38 public class WMSLayer extends AbstractCachedTileSourceLayer<TemplatedWMSTileSource> {
     38public class WMSLayer extends AbstractCachedTileSourceLayer<AbstractWMSTileSource> {
    3939    private static final String PREFERENCE_PREFIX = "imagery.wms";
    4040    /**
     
    8181
    8282    @Override
    83     protected TemplatedWMSTileSource getTileSource(ImageryInfo info) {
     83    protected AbstractWMSTileSource getTileSource(ImageryInfo info) {
    8484        if (info.getImageryType() == ImageryType.WMS && info.getUrl() != null) {
    8585            TemplatedWMSTileSource.checkUrl(info.getUrl());
    86             TemplatedWMSTileSource tileSource = new TemplatedWMSTileSource(info);
     86            AbstractWMSTileSource tileSource = new TemplatedWMSTileSource(info);
    8787            info.setAttribution(tileSource);
    8888            return tileSource;
     
    108108            ImageryLayerInfo.addLayer(new ImageryInfo(info));
    109109        }
    110     }
    111 
    112     @Override
    113     protected Map<String, String> getHeaders(TemplatedWMSTileSource tileSource) {
    114         return tileSource.getHeaders();
    115110    }
    116111
Note: See TracChangeset for help on using the changeset viewer.