001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.imagery;
003
004import java.io.IOException;
005import java.util.List;
006import java.util.Map;
007import java.util.concurrent.ConcurrentHashMap;
008import java.util.regex.Matcher;
009import java.util.regex.Pattern;
010import java.util.stream.Collectors;
011
012import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
013import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
014import org.openstreetmap.josm.data.projection.Projection;
015import org.openstreetmap.josm.gui.layer.WMSLayer;
016import org.openstreetmap.josm.io.imagery.WMSImagery;
017import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException;
018import org.openstreetmap.josm.tools.CheckParameterUtil;
019
020/**
021 * Class representing ImageryType.WMS_ENDPOINT tile source.
022 * It differs from standard WMS tile source that this tile source fetches GetCapabilities from server and
023 * uses most of the parameters from there
024 *
025 * @author Wiktor Niesiobedzki
026 * @since 13733
027 */
028public class WMSEndpointTileSource extends AbstractWMSTileSource implements TemplatedTileSource {
029
030    private final WMSImagery wmsi;
031    private final List<DefaultLayer> layers;
032    private final String urlPattern;
033    private static final Pattern PATTERN_PARAM = Pattern.compile("\\{([^}]+)\\}");
034    private final Map<String, String> headers = new ConcurrentHashMap<>();
035
036    /**
037     * Create WMSEndpointTileSource tile source
038     * @param info WMS_ENDPOINT ImageryInfo
039     * @param tileProjection server projection that should be used by this tile source
040     */
041    public WMSEndpointTileSource(ImageryInfo info, Projection tileProjection) {
042        super(info, tileProjection);
043        CheckParameterUtil.ensure(info, "imageryType", x -> ImageryType.WMS_ENDPOINT.equals(x.getImageryType()));
044        try {
045            wmsi = new WMSImagery(info.getUrl(), info.getCustomHttpHeaders());
046        } catch (IOException | WMSGetCapabilitiesException e) {
047            throw new IllegalArgumentException(e);
048        }
049        layers = info.getDefaultLayers();
050        initProjection();
051        urlPattern = wmsi.buildGetMapUrl(layers, info.isTransparent());
052        this.headers.putAll(info.getCustomHttpHeaders());
053    }
054
055    @Override
056    public int getDefaultTileSize() {
057        return WMSLayer.PROP_IMAGE_SIZE.get();
058    }
059
060    @Override
061    public String getTileUrl(int zoom, int tilex, int tiley) {
062        String bbox = getBbox(zoom, tilex, tiley, !wmsi.belowWMS130() && getTileProjection().switchXY());
063
064        // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll
065        StringBuffer url = new StringBuffer(urlPattern.length());
066        Matcher matcher = PATTERN_PARAM.matcher(urlPattern);
067        while (matcher.find()) {
068            String replacement;
069            switch (matcher.group(1)) {
070            case "proj":
071                replacement = getServerCRS();
072                break;
073            case "bbox":
074                replacement = bbox;
075                break;
076            case "width":
077            case "height":
078                replacement = String.valueOf(getTileSize());
079                break;
080            default:
081                replacement = '{' + matcher.group(1) + '}';
082            }
083            matcher.appendReplacement(url, replacement);
084        }
085        matcher.appendTail(url);
086        return url.toString();
087    }
088
089    /**
090     *
091     * @return list of EPSG codes that current layer selection supports (this may differ from layer to layer)
092     */
093    public List<String> getServerProjections() {
094        return wmsi.getLayers(layers).stream().flatMap(x -> x.getCrs().stream()).distinct().collect(Collectors.toList());
095    }
096
097    @Override
098    public Map<String, String> getHeaders() {
099        return headers;
100    }
101}