1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.imagery.vectortile.mapbox.style;
|
---|
3 |
|
---|
4 | import org.openstreetmap.josm.gui.mappaint.StyleKeys;
|
---|
5 |
|
---|
6 | import java.awt.Font;
|
---|
7 | import java.awt.GraphicsEnvironment;
|
---|
8 | import java.text.MessageFormat;
|
---|
9 | import java.util.Arrays;
|
---|
10 | import java.util.Collection;
|
---|
11 | import java.util.List;
|
---|
12 | import java.util.Locale;
|
---|
13 | import java.util.Objects;
|
---|
14 | import java.util.regex.Matcher;
|
---|
15 | import java.util.regex.Pattern;
|
---|
16 | import java.util.stream.Collectors;
|
---|
17 | import java.util.stream.Stream;
|
---|
18 |
|
---|
19 | import javax.json.JsonArray;
|
---|
20 | import javax.json.JsonNumber;
|
---|
21 | import javax.json.JsonObject;
|
---|
22 | import javax.json.JsonString;
|
---|
23 | import javax.json.JsonValue;
|
---|
24 |
|
---|
25 | /**
|
---|
26 | * Mapbox style layers
|
---|
27 | * @author Taylor Smock
|
---|
28 | * @see <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/">https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/</a>
|
---|
29 | * @since 17862
|
---|
30 | */
|
---|
31 | public class Layers {
|
---|
32 | /**
|
---|
33 | * The layer type. This affects the rendering.
|
---|
34 | * @author Taylor Smock
|
---|
35 | * @since 17862
|
---|
36 | */
|
---|
37 | enum Type {
|
---|
38 | /** Filled polygon with an (optional) border */
|
---|
39 | FILL,
|
---|
40 | /** A line */
|
---|
41 | LINE,
|
---|
42 | /** A symbol */
|
---|
43 | SYMBOL,
|
---|
44 | /** A circle */
|
---|
45 | CIRCLE,
|
---|
46 | /** A heatmap */
|
---|
47 | HEATMAP,
|
---|
48 | /** A 3D polygon extrusion */
|
---|
49 | FILL_EXTRUSION,
|
---|
50 | /** Raster */
|
---|
51 | RASTER,
|
---|
52 | /** Hillshade data */
|
---|
53 | HILLSHADE,
|
---|
54 | /** A background color or pattern */
|
---|
55 | BACKGROUND,
|
---|
56 | /** The fallback layer */
|
---|
57 | SKY
|
---|
58 | }
|
---|
59 |
|
---|
60 | private static final String EMPTY_STRING = "";
|
---|
61 | private static final char SEMI_COLON = ';';
|
---|
62 | private static final Pattern CURLY_BRACES = Pattern.compile("(\\{(.*?)})");
|
---|
63 | private static final String PAINT = "paint";
|
---|
64 |
|
---|
65 | /** A required unique layer name */
|
---|
66 | private final String id;
|
---|
67 | /** The required type */
|
---|
68 | private final Type type;
|
---|
69 | /** An optional expression */
|
---|
70 | private final Expression filter;
|
---|
71 | /** The max zoom for the layer */
|
---|
72 | private final int maxZoom;
|
---|
73 | /** The min zoom for the layer */
|
---|
74 | private final int minZoom;
|
---|
75 |
|
---|
76 | /** Default paint properties for this layer */
|
---|
77 | private final String paint;
|
---|
78 |
|
---|
79 | /** A source description to be used with this layer. Required for everything <i>but</i> {@link Type#BACKGROUND} */
|
---|
80 | private final String source;
|
---|
81 | /** Layer to use from the vector tile source. Only allowed with {@link SourceType#VECTOR}. */
|
---|
82 | private final String sourceLayer;
|
---|
83 | /** The id for the style -- used for image paths */
|
---|
84 | private final String styleId;
|
---|
85 | /**
|
---|
86 | * Create a layer object
|
---|
87 | * @param layerInfo The info to use to create the layer
|
---|
88 | */
|
---|
89 | public Layers(final JsonObject layerInfo) {
|
---|
90 | this (null, layerInfo);
|
---|
91 | }
|
---|
92 |
|
---|
93 | /**
|
---|
94 | * Create a layer object
|
---|
95 | * @param styleId The id for the style (image paths require this)
|
---|
96 | * @param layerInfo The info to use to create the layer
|
---|
97 | */
|
---|
98 | public Layers(final String styleId, final JsonObject layerInfo) {
|
---|
99 | this.id = layerInfo.getString("id");
|
---|
100 | this.styleId = styleId;
|
---|
101 | this.type = Type.valueOf(layerInfo.getString("type").replace("-", "_").toUpperCase(Locale.ROOT));
|
---|
102 | if (layerInfo.containsKey("filter")) {
|
---|
103 | this.filter = new Expression(layerInfo.get("filter"));
|
---|
104 | } else {
|
---|
105 | this.filter = Expression.EMPTY_EXPRESSION;
|
---|
106 | }
|
---|
107 | this.maxZoom = layerInfo.getInt("maxzoom", Integer.MAX_VALUE);
|
---|
108 | this.minZoom = layerInfo.getInt("minzoom", Integer.MIN_VALUE);
|
---|
109 | // There is a metadata field (I don't *think* I need it?)
|
---|
110 | // source is only optional with {@link Type#BACKGROUND}.
|
---|
111 | if (this.type == Type.BACKGROUND) {
|
---|
112 | this.source = layerInfo.getString("source", null);
|
---|
113 | } else {
|
---|
114 | this.source = layerInfo.getString("source");
|
---|
115 | }
|
---|
116 | if (layerInfo.containsKey(PAINT) && layerInfo.get(PAINT).getValueType() == JsonValue.ValueType.OBJECT) {
|
---|
117 | final JsonObject paintObject = layerInfo.getJsonObject(PAINT);
|
---|
118 | final JsonObject layoutObject = layerInfo.getOrDefault("layout", JsonValue.EMPTY_JSON_OBJECT).asJsonObject();
|
---|
119 | // Don't throw exceptions here, since we may just point at the styling
|
---|
120 | if ("visible".equalsIgnoreCase(layoutObject.getString("visibility", "visible"))) {
|
---|
121 | switch (type) {
|
---|
122 | case FILL:
|
---|
123 | // area
|
---|
124 | this.paint = parsePaintFill(paintObject);
|
---|
125 | break;
|
---|
126 | case LINE:
|
---|
127 | // way
|
---|
128 | this.paint = parsePaintLine(layoutObject, paintObject);
|
---|
129 | break;
|
---|
130 | case CIRCLE:
|
---|
131 | // point
|
---|
132 | this.paint = parsePaintCircle(paintObject);
|
---|
133 | break;
|
---|
134 | case SYMBOL:
|
---|
135 | // point
|
---|
136 | this.paint = parsePaintSymbol(layoutObject, paintObject);
|
---|
137 | break;
|
---|
138 | case BACKGROUND:
|
---|
139 | // canvas only
|
---|
140 | this.paint = parsePaintBackground(paintObject);
|
---|
141 | break;
|
---|
142 | default:
|
---|
143 | this.paint = EMPTY_STRING;
|
---|
144 | }
|
---|
145 | } else {
|
---|
146 | this.paint = EMPTY_STRING;
|
---|
147 | }
|
---|
148 | } else {
|
---|
149 | this.paint = EMPTY_STRING;
|
---|
150 | }
|
---|
151 | this.sourceLayer = layerInfo.getString("source-layer", null);
|
---|
152 | }
|
---|
153 |
|
---|
154 | /**
|
---|
155 | * Get the filter for this layer
|
---|
156 | * @return The filter
|
---|
157 | */
|
---|
158 | public Expression getFilter() {
|
---|
159 | return this.filter;
|
---|
160 | }
|
---|
161 |
|
---|
162 | /**
|
---|
163 | * Get the unique id for this layer
|
---|
164 | * @return The unique id
|
---|
165 | */
|
---|
166 | public String getId() {
|
---|
167 | return this.id;
|
---|
168 | }
|
---|
169 |
|
---|
170 | /**
|
---|
171 | * Get the type of this layer
|
---|
172 | * @return The layer type
|
---|
173 | */
|
---|
174 | public Type getType() {
|
---|
175 | return this.type;
|
---|
176 | }
|
---|
177 |
|
---|
178 | private static String parsePaintLine(final JsonObject layoutObject, final JsonObject paintObject) {
|
---|
179 | final StringBuilder sb = new StringBuilder(36);
|
---|
180 | // line-blur, default 0 (px)
|
---|
181 | // line-color, default #000000, disabled by line-pattern
|
---|
182 | final String color = paintObject.getString("line-color", "#000000");
|
---|
183 | sb.append(StyleKeys.COLOR).append(':').append(color).append(SEMI_COLON);
|
---|
184 | // line-opacity, default 1 (0-1)
|
---|
185 | final JsonNumber opacity = paintObject.getJsonNumber("line-opacity");
|
---|
186 | if (opacity != null) {
|
---|
187 | sb.append(StyleKeys.OPACITY).append(':').append(opacity.numberValue().doubleValue()).append(SEMI_COLON);
|
---|
188 | }
|
---|
189 | // line-cap, default butt (butt|round|square)
|
---|
190 | final String cap = layoutObject.getString("line-cap", "butt");
|
---|
191 | sb.append(StyleKeys.LINECAP).append(':');
|
---|
192 | switch (cap) {
|
---|
193 | case "round":
|
---|
194 | case "square":
|
---|
195 | sb.append(cap);
|
---|
196 | break;
|
---|
197 | case "butt":
|
---|
198 | default:
|
---|
199 | sb.append("none");
|
---|
200 | }
|
---|
201 |
|
---|
202 | sb.append(SEMI_COLON);
|
---|
203 | // line-dasharray, array of number >= 0, units in line widths, disabled by line-pattern
|
---|
204 | if (paintObject.containsKey("line-dasharray")) {
|
---|
205 | final JsonArray dashArray = paintObject.getJsonArray("line-dasharray");
|
---|
206 | sb.append(StyleKeys.DASHES).append(':');
|
---|
207 | sb.append(dashArray.stream().filter(JsonNumber.class::isInstance).map(JsonNumber.class::cast)
|
---|
208 | .map(JsonNumber::toString).collect(Collectors.joining(",")));
|
---|
209 | sb.append(SEMI_COLON);
|
---|
210 | }
|
---|
211 | // line-gap-width
|
---|
212 | // line-gradient
|
---|
213 | // line-join
|
---|
214 | // line-miter-limit
|
---|
215 | // line-offset
|
---|
216 | // line-pattern TODO this first, since it disables stuff
|
---|
217 | // line-round-limit
|
---|
218 | // line-sort-key
|
---|
219 | // line-translate
|
---|
220 | // line-translate-anchor
|
---|
221 | // line-width
|
---|
222 | final JsonNumber width = paintObject.getJsonNumber("line-width");
|
---|
223 | sb.append(StyleKeys.WIDTH).append(':').append(width == null ? 1 : width.toString()).append(SEMI_COLON);
|
---|
224 | return sb.toString();
|
---|
225 | }
|
---|
226 |
|
---|
227 | private static String parsePaintCircle(final JsonObject paintObject) {
|
---|
228 | final StringBuilder sb = new StringBuilder(150).append("symbol-shape:circle;")
|
---|
229 | // circle-blur
|
---|
230 | // circle-color
|
---|
231 | .append("symbol-fill-color:").append(paintObject.getString("circle-color", "#000000")).append(SEMI_COLON);
|
---|
232 | // circle-opacity
|
---|
233 | final JsonNumber fillOpacity = paintObject.getJsonNumber("circle-opacity");
|
---|
234 | sb.append("symbol-fill-opacity:").append(fillOpacity != null ? fillOpacity.numberValue().toString() : "1").append(SEMI_COLON);
|
---|
235 | // circle-pitch-alignment // not 3D
|
---|
236 | // circle-pitch-scale // not 3D
|
---|
237 | // circle-radius
|
---|
238 | final JsonNumber radius = paintObject.getJsonNumber("circle-radius");
|
---|
239 | sb.append("symbol-size:").append(radius != null ? (2 * radius.numberValue().doubleValue()) : "10").append(SEMI_COLON)
|
---|
240 | // circle-sort-key
|
---|
241 | // circle-stroke-color
|
---|
242 | .append("symbol-stroke-color:").append(paintObject.getString("circle-stroke-color", "#000000")).append(SEMI_COLON);
|
---|
243 | // circle-stroke-opacity
|
---|
244 | final JsonNumber strokeOpacity = paintObject.getJsonNumber("circle-stroke-opacity");
|
---|
245 | sb.append("symbol-stroke-opacity:").append(strokeOpacity != null ? strokeOpacity.numberValue().toString() : "1").append(SEMI_COLON);
|
---|
246 | // circle-stroke-width
|
---|
247 | final JsonNumber strokeWidth = paintObject.getJsonNumber("circle-stroke-width");
|
---|
248 | sb.append("symbol-stroke-width:").append(strokeWidth != null ? strokeWidth.numberValue().toString() : "0").append(SEMI_COLON);
|
---|
249 | // circle-translate
|
---|
250 | // circle-translate-anchor
|
---|
251 | return sb.toString();
|
---|
252 | }
|
---|
253 |
|
---|
254 | private String parsePaintSymbol(
|
---|
255 | final JsonObject layoutObject,
|
---|
256 | final JsonObject paintObject) {
|
---|
257 | final StringBuilder sb = new StringBuilder();
|
---|
258 | // icon-allow-overlap
|
---|
259 | // icon-anchor
|
---|
260 | // icon-color
|
---|
261 | // icon-halo-blur
|
---|
262 | // icon-halo-color
|
---|
263 | // icon-halo-width
|
---|
264 | // icon-ignore-placement
|
---|
265 | // icon-image
|
---|
266 | boolean iconImage = false;
|
---|
267 | if (layoutObject.containsKey("icon-image")) {
|
---|
268 | sb.append(/* NO-ICON */"icon-image:concat(");
|
---|
269 | if (this.styleId != null && !this.styleId.trim().isEmpty()) {
|
---|
270 | sb.append('"').append(this.styleId).append('/').append("\",");
|
---|
271 | }
|
---|
272 | Matcher matcher = CURLY_BRACES.matcher(layoutObject.getString("icon-image"));
|
---|
273 | StringBuffer stringBuffer = new StringBuffer();
|
---|
274 | int previousMatch;
|
---|
275 | if (matcher.lookingAt()) {
|
---|
276 | matcher.appendReplacement(stringBuffer, "tag(\"$2\"),\"");
|
---|
277 | previousMatch = matcher.end();
|
---|
278 | } else {
|
---|
279 | previousMatch = 0;
|
---|
280 | stringBuffer.append('"');
|
---|
281 | }
|
---|
282 | while (matcher.find()) {
|
---|
283 | if (matcher.start() == previousMatch) {
|
---|
284 | matcher.appendReplacement(stringBuffer, ",tag(\"$2\")");
|
---|
285 | } else {
|
---|
286 | matcher.appendReplacement(stringBuffer, "\",tag(\"$2\"),\"");
|
---|
287 | }
|
---|
288 | previousMatch = matcher.end();
|
---|
289 | }
|
---|
290 | if (matcher.hitEnd() && stringBuffer.toString().endsWith(",\"")) {
|
---|
291 | stringBuffer.delete(stringBuffer.length() - ",\"".length(), stringBuffer.length());
|
---|
292 | } else if (!matcher.hitEnd()) {
|
---|
293 | stringBuffer.append('"');
|
---|
294 | }
|
---|
295 | StringBuffer tail = new StringBuffer();
|
---|
296 | matcher.appendTail(tail);
|
---|
297 | if (tail.length() > 0) {
|
---|
298 | String current = stringBuffer.toString();
|
---|
299 | if (!"\"".equals(current) && !current.endsWith(",\"")) {
|
---|
300 | stringBuffer.append(",\"");
|
---|
301 | }
|
---|
302 | stringBuffer.append(tail);
|
---|
303 | stringBuffer.append('"');
|
---|
304 | }
|
---|
305 |
|
---|
306 | sb.append(stringBuffer).append(')').append(SEMI_COLON);
|
---|
307 | iconImage = true;
|
---|
308 | }
|
---|
309 | // icon-keep-upright
|
---|
310 | // icon-offset
|
---|
311 | if (iconImage && layoutObject.containsKey("icon-offset")) {
|
---|
312 | // default [0, 0], right,down == positive, left,up == negative
|
---|
313 | final List<JsonNumber> offset = layoutObject.getJsonArray("icon-offset").getValuesAs(JsonNumber.class);
|
---|
314 | // Assume that the offset must be size 2. Probably not necessary, but docs aren't necessary clear.
|
---|
315 | if (offset.size() == 2) {
|
---|
316 | sb.append("icon-offset-x:").append(offset.get(0).doubleValue()).append(SEMI_COLON)
|
---|
317 | .append("icon-offset-y:").append(offset.get(1).doubleValue()).append(SEMI_COLON);
|
---|
318 | }
|
---|
319 | }
|
---|
320 | // icon-opacity
|
---|
321 | if (iconImage && paintObject.containsKey("icon-opacity")) {
|
---|
322 | final double opacity = paintObject.getJsonNumber("icon-opacity").doubleValue();
|
---|
323 | sb.append("icon-opacity:").append(opacity).append(SEMI_COLON);
|
---|
324 | }
|
---|
325 | // icon-optional
|
---|
326 | // icon-padding
|
---|
327 | // icon-pitch-alignment
|
---|
328 | // icon-rotate
|
---|
329 | if (iconImage && layoutObject.containsKey("icon-rotate")) {
|
---|
330 | final double rotation = layoutObject.getJsonNumber("icon-rotate").doubleValue();
|
---|
331 | sb.append("icon-rotation:").append(rotation).append(SEMI_COLON);
|
---|
332 | }
|
---|
333 | // icon-rotation-alignment
|
---|
334 | // icon-size
|
---|
335 | // icon-text-fit
|
---|
336 | // icon-text-fit-padding
|
---|
337 | // icon-translate
|
---|
338 | // icon-translate-anchor
|
---|
339 | // symbol-avoid-edges
|
---|
340 | // symbol-placement
|
---|
341 | // symbol-sort-key
|
---|
342 | // symbol-spacing
|
---|
343 | // symbol-z-order
|
---|
344 | // text-allow-overlap
|
---|
345 | // text-anchor
|
---|
346 | // text-color
|
---|
347 | if (paintObject.containsKey(StyleKeys.TEXT_COLOR)) {
|
---|
348 | sb.append(StyleKeys.TEXT_COLOR).append(':').append(paintObject.getString(StyleKeys.TEXT_COLOR)).append(SEMI_COLON);
|
---|
349 | }
|
---|
350 | // text-field
|
---|
351 | if (layoutObject.containsKey("text-field")) {
|
---|
352 | sb.append(StyleKeys.TEXT).append(':')
|
---|
353 | .append(layoutObject.getString("text-field").replace("}", EMPTY_STRING).replace("{", EMPTY_STRING))
|
---|
354 | .append(SEMI_COLON);
|
---|
355 | }
|
---|
356 | // text-font
|
---|
357 | if (layoutObject.containsKey("text-font")) {
|
---|
358 | List<String> fonts = layoutObject.getJsonArray("text-font").stream().filter(JsonString.class::isInstance)
|
---|
359 | .map(JsonString.class::cast).map(JsonString::getString).collect(Collectors.toList());
|
---|
360 | Font[] systemFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
---|
361 | for (String fontString : fonts) {
|
---|
362 | Collection<Font> fontMatches = Stream.of(systemFonts)
|
---|
363 | .filter(font -> Arrays.asList(font.getName(), font.getFontName(), font.getFamily(), font.getPSName()).contains(fontString))
|
---|
364 | .collect(Collectors.toList());
|
---|
365 | if (!fontMatches.isEmpty()) {
|
---|
366 | final Font setFont = fontMatches.stream().filter(font -> font.getName().equals(fontString)).findAny()
|
---|
367 | .orElseGet(() -> fontMatches.stream().filter(font -> font.getFontName().equals(fontString)).findAny()
|
---|
368 | .orElseGet(() -> fontMatches.stream().filter(font -> font.getPSName().equals(fontString)).findAny()
|
---|
369 | .orElseGet(() -> fontMatches.stream().filter(font -> font.getFamily().equals(fontString)).findAny().orElse(null))));
|
---|
370 | if (setFont != null) {
|
---|
371 | sb.append(StyleKeys.FONT_FAMILY).append(':').append('"').append(setFont.getFamily()).append('"').append(SEMI_COLON);
|
---|
372 | sb.append(StyleKeys.FONT_WEIGHT).append(':').append(setFont.isBold() ? "bold" : "normal").append(SEMI_COLON);
|
---|
373 | sb.append(StyleKeys.FONT_STYLE).append(':').append(setFont.isItalic() ? "italic" : "normal").append(SEMI_COLON);
|
---|
374 | break;
|
---|
375 | }
|
---|
376 | }
|
---|
377 | }
|
---|
378 | }
|
---|
379 | // text-halo-blur
|
---|
380 | // text-halo-color
|
---|
381 | if (paintObject.containsKey(StyleKeys.TEXT_HALO_COLOR)) {
|
---|
382 | sb.append(StyleKeys.TEXT_HALO_COLOR).append(':').append(paintObject.getString(StyleKeys.TEXT_HALO_COLOR)).append(SEMI_COLON);
|
---|
383 | }
|
---|
384 | // text-halo-width
|
---|
385 | if (paintObject.containsKey("text-halo-width")) {
|
---|
386 | sb.append(StyleKeys.TEXT_HALO_RADIUS).append(':').append(paintObject.getJsonNumber("text-halo-width").intValue() / 2)
|
---|
387 | .append(SEMI_COLON);
|
---|
388 | }
|
---|
389 | // text-ignore-placement
|
---|
390 | // text-justify
|
---|
391 | // text-keep-upright
|
---|
392 | // text-letter-spacing
|
---|
393 | // text-line-height
|
---|
394 | // text-max-angle
|
---|
395 | // text-max-width
|
---|
396 | // text-offset
|
---|
397 | // text-opacity
|
---|
398 | if (paintObject.containsKey(StyleKeys.TEXT_OPACITY)) {
|
---|
399 | sb.append(StyleKeys.TEXT_OPACITY).append(':').append(paintObject.getJsonNumber(StyleKeys.TEXT_OPACITY).doubleValue())
|
---|
400 | .append(SEMI_COLON);
|
---|
401 | }
|
---|
402 | // text-optional
|
---|
403 | // text-padding
|
---|
404 | // text-pitch-alignment
|
---|
405 | // text-radial-offset
|
---|
406 | // text-rotate
|
---|
407 | // text-rotation-alignment
|
---|
408 | // text-size
|
---|
409 | final JsonNumber textSize = layoutObject.getJsonNumber("text-size");
|
---|
410 | sb.append(StyleKeys.FONT_SIZE).append(':').append(textSize != null ? textSize.numberValue().toString() : "16").append(SEMI_COLON);
|
---|
411 | // text-transform
|
---|
412 | // text-translate
|
---|
413 | // text-translate-anchor
|
---|
414 | // text-variable-anchor
|
---|
415 | // text-writing-mode
|
---|
416 | return sb.toString();
|
---|
417 | }
|
---|
418 |
|
---|
419 | private static String parsePaintBackground(final JsonObject paintObject) {
|
---|
420 | final StringBuilder sb = new StringBuilder(20);
|
---|
421 | // background-color
|
---|
422 | final String bgColor = paintObject.getString("background-color", null);
|
---|
423 | if (bgColor != null) {
|
---|
424 | sb.append(StyleKeys.FILL_COLOR).append(':').append(bgColor).append(SEMI_COLON);
|
---|
425 | }
|
---|
426 | // background-opacity
|
---|
427 | // background-pattern
|
---|
428 | return sb.toString();
|
---|
429 | }
|
---|
430 |
|
---|
431 | private static String parsePaintFill(final JsonObject paintObject) {
|
---|
432 | StringBuilder sb = new StringBuilder(50)
|
---|
433 | // fill-antialias
|
---|
434 | // fill-color
|
---|
435 | .append(StyleKeys.FILL_COLOR).append(':').append(paintObject.getString(StyleKeys.FILL_COLOR, "#000000")).append(SEMI_COLON);
|
---|
436 | // fill-opacity
|
---|
437 | final JsonNumber opacity = paintObject.getJsonNumber(StyleKeys.FILL_OPACITY);
|
---|
438 | sb.append(StyleKeys.FILL_OPACITY).append(':').append(opacity != null ? opacity.numberValue().toString() : "1").append(SEMI_COLON)
|
---|
439 | // fill-outline-color
|
---|
440 | .append(StyleKeys.COLOR).append(':').append(paintObject.getString("fill-outline-color",
|
---|
441 | paintObject.getString("fill-color", "#000000"))).append(SEMI_COLON);
|
---|
442 | // fill-pattern
|
---|
443 | // fill-sort-key
|
---|
444 | // fill-translate
|
---|
445 | // fill-translate-anchor
|
---|
446 | return sb.toString();
|
---|
447 | }
|
---|
448 |
|
---|
449 | /**
|
---|
450 | * Converts this layer object to a mapcss entry string (to be parsed later)
|
---|
451 | * @return The mapcss entry (string form)
|
---|
452 | */
|
---|
453 | @Override
|
---|
454 | public String toString() {
|
---|
455 | if (this.filter.toString().isEmpty() && this.paint.isEmpty()) {
|
---|
456 | return EMPTY_STRING;
|
---|
457 | } else if (this.type == Type.BACKGROUND) {
|
---|
458 | // AFAIK, paint has no zoom levels, and doesn't accept a layer
|
---|
459 | return "canvas{" + this.paint + "}";
|
---|
460 | }
|
---|
461 |
|
---|
462 | final String zoomSelector;
|
---|
463 | if (this.minZoom == this.maxZoom) {
|
---|
464 | zoomSelector = "|z" + this.minZoom;
|
---|
465 | } else if (this.minZoom > Integer.MIN_VALUE && this.maxZoom == Integer.MAX_VALUE) {
|
---|
466 | zoomSelector = "|z" + this.minZoom + "-";
|
---|
467 | } else if (this.minZoom == Integer.MIN_VALUE && this.maxZoom < Integer.MAX_VALUE) {
|
---|
468 | zoomSelector = "|z-" + this.maxZoom;
|
---|
469 | } else if (this.minZoom > Integer.MIN_VALUE) {
|
---|
470 | zoomSelector = MessageFormat.format("|z{0}-{1}", this.minZoom, this.maxZoom);
|
---|
471 | } else {
|
---|
472 | zoomSelector = EMPTY_STRING;
|
---|
473 | }
|
---|
474 | final String commonData = zoomSelector + this.filter.toString() + "::" + this.id + "{" + this.paint + "}";
|
---|
475 |
|
---|
476 | if (this.type == Type.CIRCLE || this.type == Type.SYMBOL) {
|
---|
477 | return "node" + commonData;
|
---|
478 | } else if (this.type == Type.FILL) {
|
---|
479 | return "area" + commonData;
|
---|
480 | } else if (this.type == Type.LINE) {
|
---|
481 | return "way" + commonData;
|
---|
482 | }
|
---|
483 | return super.toString();
|
---|
484 | }
|
---|
485 |
|
---|
486 | /**
|
---|
487 | * Get the source that this applies to
|
---|
488 | * @return The source name
|
---|
489 | */
|
---|
490 | public String getSource() {
|
---|
491 | return this.source;
|
---|
492 | }
|
---|
493 |
|
---|
494 | /**
|
---|
495 | * Get the layer that this applies to
|
---|
496 | * @return The layer name
|
---|
497 | */
|
---|
498 | public String getSourceLayer() {
|
---|
499 | return this.sourceLayer;
|
---|
500 | }
|
---|
501 |
|
---|
502 | @Override
|
---|
503 | public boolean equals(Object other) {
|
---|
504 | if (other != null && this.getClass() == other.getClass()) {
|
---|
505 | Layers o = (Layers) other;
|
---|
506 | return this.type == o.type
|
---|
507 | && this.minZoom == o.minZoom
|
---|
508 | && this.maxZoom == o.maxZoom
|
---|
509 | && Objects.equals(this.id, o.id)
|
---|
510 | && Objects.equals(this.styleId, o.styleId)
|
---|
511 | && Objects.equals(this.sourceLayer, o.sourceLayer)
|
---|
512 | && Objects.equals(this.source, o.source)
|
---|
513 | && Objects.equals(this.filter, o.filter)
|
---|
514 | && Objects.equals(this.paint, o.paint);
|
---|
515 | }
|
---|
516 | return false;
|
---|
517 | }
|
---|
518 |
|
---|
519 | @Override
|
---|
520 | public int hashCode() {
|
---|
521 | return Objects.hash(this.type, this.minZoom, this.maxZoom, this.id, this.styleId, this.sourceLayer, this.source,
|
---|
522 | this.filter, this.paint);
|
---|
523 | }
|
---|
524 | }
|
---|