001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.imagery;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.List;
007import java.util.Map;
008import java.util.concurrent.ConcurrentHashMap;
009import java.util.stream.Stream;
010
011import org.openstreetmap.josm.data.Bounds;
012
013/**
014 * The details of a layer of this WMS server.
015 */
016public class LayerDetails {
017    private final Map<String, String> styles = new ConcurrentHashMap<>(); // name -> title
018    private final Collection<String> crs = new ArrayList<>();
019    /**
020     * The layer name (WMS {@code Title})
021     */
022    private String title;
023    /**
024     * The layer name (WMS {@code Name})
025     */
026    private String name;
027    /**
028     * The layer abstract (WMS {@code Abstract})
029     * @since 13199
030     */
031    private String abstr;
032    private final LayerDetails parentLayer;
033    private Bounds bounds;
034    private List<LayerDetails> children = new ArrayList<>();
035
036    /**
037     * Constructor pointing to parent layer. Set to null if this is topmost layer.
038     * This is needed to properly handle layer attributes inheritance.
039     *
040     * @param parentLayer parent layer
041     */
042    public LayerDetails(LayerDetails parentLayer) {
043        this.parentLayer = parentLayer;
044    }
045
046    /**
047     * @return projections that are supported by this layer
048     */
049    public Collection<String> getCrs() {
050        Collection<String> ret = new ArrayList<>();
051        if (parentLayer != null) {
052            ret.addAll(parentLayer.getCrs());
053        }
054        ret.addAll(crs);
055        return ret;
056    }
057
058    /**
059     *
060     * @return styles defined for this layer
061     */
062    public Map<String, String> getStyles() {
063        Map<String, String> ret = new ConcurrentHashMap<>();
064        if (parentLayer != null) {
065            ret.putAll(parentLayer.getStyles());
066        }
067        ret.putAll(styles);
068        return ret;
069    }
070
071    /**
072     * @return title "Human readable" title of this layer
073     * @see LayerDetails#getName()
074     */
075    public String getTitle() {
076        return title;
077    }
078
079    /**
080     * @param title set title of this layer
081     * @see LayerDetails#getName()
082     */
083    public void setTitle(String title) {
084        this.title = title;
085    }
086
087    /**
088     *
089     * Citation from OGC WMS specification (WMS 1.3.0):<p><i>
090     * A number of elements have both a {@literal <Name>} and a {@literal <Title>}. The Name is a text string used for machine-to-machine
091     * communication while the Title is for the benefit of humans. For example, a dataset might have the descriptive Title
092     * “Maximum Atmospheric Temperature” and be requested using the abbreviated Name “ATMAX”.</i></p>
093     *
094     * And second citation:<p><i>
095     * If, and only if, a layer has a {@literal <Name>}, then it is a map layer that can be requested by using that Name in the
096     * LAYERS parameter of a GetMap request. A Layer that contains a {@literal <Name>} element is referred to as a “named
097     * layer” in this International Standard. If the layer has a Title but no Name, then that layer is only a category title for
098     * all the layers nested within.</i></p>
099     * @return name of this layer
100     */
101    public String getName() {
102        return name;
103    }
104
105    /**
106     * @param name sets the name of this Layer
107     * @see LayerDetails#getName()
108     */
109    public void setName(String name) {
110        this.name = name;
111    }
112
113    /**
114     * Add style to list of styles defined by this layer
115     * @param name machine-to-machine name of this style
116     * @param title human readable title of this style
117     */
118    public void addStyle(String name, String title) {
119        this.styles.put(name, title == null ? "" : title);
120    }
121
122    /**
123     * Add projection supported by this layer
124     * @param crs projection code
125     */
126    public void addCrs(String crs) {
127        this.crs.add(crs);
128    }
129
130    /**
131     *
132     * @return bounds within layer might be queried
133     */
134    public Bounds getBounds() {
135        return bounds;
136    }
137
138    /**
139     * sets bounds of this layer
140     * @param bounds of this layer
141     */
142    public void setBounds(Bounds bounds) {
143        this.bounds = bounds;
144    }
145
146    @Override
147    public String toString() {
148        String baseName = (title == null || title.isEmpty()) ? name : title;
149        return abstr == null || abstr.equalsIgnoreCase(baseName) ? baseName : baseName + " (" + abstr + ')';
150    }
151
152    /**
153     *
154     * @return parent layer for his layer
155     */
156    public LayerDetails getParent() {
157        return parentLayer;
158    }
159
160    /**
161     * sets children layers for this layer
162     * @param children children of this layer
163     */
164    public void setChildren(List<LayerDetails> children) {
165        this.children = children;
166
167    }
168
169    /**
170     *
171     * @return children layers of this layer
172     */
173    public List<LayerDetails> getChildren() {
174        return children;
175    }
176
177    /**
178     * if user may select this layer (is it possible to request it from server)
179     * @return true if user may select this layer, false if this layer is only grouping other layers
180     */
181    public boolean isSelectable() {
182        return !(name == null || name.isEmpty());
183    }
184
185    /**
186     * @return "Narrative description of the layer"
187     */
188    public String getAbstract() {
189        return abstr;
190    }
191
192    /**
193     * Sets abstract of this layer
194     * @param abstr abstract of this layer
195     */
196    public void setAbstract(String abstr) {
197        this.abstr = abstr;
198    }
199
200    /**
201     * @return flattened stream of this layer and its children (as well as recursively children of its children)
202     */
203    public Stream<LayerDetails> flattened() {
204        return Stream.concat(
205                Stream.of(this),
206                getChildren().stream().flatMap(LayerDetails::flattened)
207                );
208    }
209}