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