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.ensureThat(info.getImageryType() == ImageryType.WMS_ENDPOINT, "imageryType");
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        // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll
063        StringBuffer url = new StringBuffer(urlPattern.length());
064        Matcher matcher = PATTERN_PARAM.matcher(urlPattern);
065        while (matcher.find()) {
066            String replacement;
067            switch (matcher.group(1)) {
068            case "proj":
069                replacement = getServerCRS();
070                break;
071            case "bbox":
072                replacement = getBbox(zoom, tilex, tiley, !wmsi.belowWMS130() && getTileProjection().switchXY());
073                break;
074            case "width":
075            case "height":
076                replacement = String.valueOf(getTileSize());
077                break;
078            default:
079                replacement = '{' + matcher.group(1) + '}';
080            }
081            matcher.appendReplacement(url, replacement);
082        }
083        matcher.appendTail(url);
084        return url.toString();
085    }
086
087    /**
088     * Returns list of EPSG codes that current layer selection supports.
089     * @return list of EPSG codes that current layer selection supports (this may differ from layer to layer)
090     */
091    public List<String> getServerProjections() {
092        return wmsi.getLayers(layers).stream().flatMap(x -> x.getCrs().stream()).distinct().collect(Collectors.toList());
093    }
094
095    @Override
096    public Map<String, String> getHeaders() {
097        return headers;
098    }
099}